Available Rentals
Live Listings

Find Your Next Home

Handpicked rentals updated in real time — schedule a showing in minutes.

Available Now
Starting From
Cities
Filter
Current Availability

Fetching live listings…

// Fetch pre-parsed listings from worker cache — fast! async function fetchViaAPI() { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 10000); try { const res = await fetch(LISTINGS_URL, { signal: controller.signal }); clearTimeout(timeout); if (!res.ok) throw new Error('HTTP ' + res.status); const listings = await res.json(); console.log('Loaded', listings.length, 'listings from cache'); return listings; } catch(e) { clearTimeout(timeout); throw e; } } // ── RENDER ──────────────────────────────────────────────────────────────────── function buildFilterChips(listings) { const cities = [...new Set(listings.map(l => l.city).filter(Boolean))]; const chips = document.getElementById('filterChips'); chips.innerHTML = ''; cities.forEach(city => { const b = document.createElement('button'); b.className = 'chip'; b.dataset.filter = city; b.textContent = city; chips.appendChild(b); }); chips.addEventListener('click', e => { if (!e.target.matches('.chip')) return; chips.querySelectorAll('.chip').forEach(c => c.classList.remove('active')); e.target.classList.add('active'); activeFilter = e.target.dataset.filter; renderCards(); }); } function updateStats(listings) { const cities = new Set(listings.map(l => l.city).filter(Boolean)); const prices = listings.map(l => l.price).filter(Boolean); document.getElementById('statCount').textContent = listings.length; document.getElementById('statMin').textContent = prices.length ? '$' + Math.min(...prices).toLocaleString() : '—'; document.getElementById('statCity').textContent = cities.size || '—'; } function cardHTML(l) { const imgs = l.images || []; let imageBlock = ''; if (imgs.length > 0) { const slidesHTML = imgs.map(url => { const proxied = imgProxy(url); return '
' + l.address.replace(/
'; }).join(''); imageBlock = '
' + slidesHTML + '
'; if (imgs.length > 1) { imageBlock += ''; imageBlock += ''; imageBlock += '
1 / ' + imgs.length + '
'; } } else { imageBlock = '
🏠
'; } const tags = (l.amenities || []).slice(0, 5).map(a => `${a}`).join(''); const pet = l.petFriendly ? '🐾 Pets OK' : ''; return `
${l.type || 'Rental'}
${imageBlock}
$${l.price.toLocaleString()}/ mo
${l.address}
${l.city}, ${l.state} ${l.zip}
${l.beds}
Bed${l.beds!==1?'s':''}
${l.baths}
Bath${l.baths!==1?'s':''}
${l.sqft ? `
${l.sqft.toLocaleString()}
Sq Ft
` : ''}
${tags}${pet}
${l.deposit ? `
Security deposit: $${l.deposit.toLocaleString()}
` : ''}
Schedule Showing ${l.applyUrl ? `Apply` : ''}
`; } function renderCards() { const grid = document.getElementById('grid'); let filtered = allListings; if (activeFilter !== 'all') filtered = filtered.filter(l => l.city === activeFilter); if (activeSort === 'price-asc') filtered = [...filtered].sort((a,b) => a.price - b.price); if (activeSort === 'price-desc') filtered = [...filtered].sort((a,b) => b.price - a.price); if (activeSort === 'sqft-desc') filtered = [...filtered].sort((a,b) => b.sqft - a.sqft); document.getElementById('sectionLabel').textContent = `${filtered.length} Propert${filtered.length===1?'y':'ies'} Available`; grid.innerHTML = filtered.length ? filtered.map(cardHTML).join('') : '

No listings match your filter.

'; initSlideshows(); } // ── AUTO-REFRESH ────────────────────────────────────────────────────────────── const REFRESH_SECS = 10 * 60; let countdown = REFRESH_SECS; const badge = document.getElementById('liveBadgeText'); const fmt = s => `Refreshes in ${Math.floor(s/60)}:${String(s%60).padStart(2,'0')}`; setInterval(() => { countdown--; if (countdown <= 0) { badge.textContent = 'Refreshing…'; load(true); } else badge.textContent = fmt(countdown); }, 1000); document.getElementById('sortSelect').addEventListener('change', e => { activeSort = e.target.value; renderCards(); }); // ── INIT ────────────────────────────────────────────────────────────────────── async function load(silent = false) { // Show fallback instantly so page never looks empty if (!allListings.length) { allListings = getFallback(); buildFilterChips(allListings); updateStats(allListings); renderCards(); } if (!silent) { badge.textContent = 'Updating…'; } try { const listings = await fetchViaAPI(); if (listings && listings.length) { allListings = listings; buildFilterChips(allListings); updateStats(allListings); renderCards(); } } catch(err) { console.warn('Live fetch failed, showing cached data:', err.message); } countdown = REFRESH_SECS; badge.textContent = fmt(countdown); } load();