openvibers/openvibe.rent Commits

Homes' largest cities are bars, like Rent's (#186)

OP OpenVibers committed 11 days ago · 9774ad8a30 · 3 files, +5 −18

Changes

ddiff --git a/server/homes/pages.js b/server/homes/pages.js
iindex 24daf6f..06104ea 100644
---- a/server/homes/pages.js
++++ b/server/homes/pages.js
@@@ -112,7 +112,7 @@ ${home.stats([
{ value: Object.keys(US_STATE_NAMES).length, label: 'states with county records', href: '/records' },
], { label: 'OpenVibe.Homes in numbers' })}
--<section class="sc-sec home-sec home-cities">${home.cityRail({ id: 'cities', title: 'What homes are worth in the largest cities', more: { href: '/in', label: `All ${cities.cities.length} cities` }, list: largest, pick: (c) => ({ corner: c.value != null ? cities.valueText(c.value) : null, peek: c.rent.b2 != null ? `2-bedroom rent ${cities.rentText(c.rent.b2)}` : '' }) })}
++<section class="sc-sec home-sec home-cities">${home.cityBars({ id: 'cities', title: 'What homes are worth in the largest cities', more: { href: '/in', label: `All ${cities.cities.length} cities` }, list: largest, value: (c) => (c.value != null ? Number(c.value) : NaN), text: (c) => cities.valueText(c.value) })}
<p class="small muted home-under">The median value of owner-occupied homes (Census Bureau, ${cities.SOURCE.years}); each city's page has the price trend, the monthly payment and every for-sale site there.</p>
${trend ? home.fold('moves', 'Where home prices moved over the past year', moves.latest, html`<p class="small muted">The FHFA House Price Index, ${moves.latest} against a year before: an index of repeat sales of the same homes, not a price. <a href="${moves.source.url}" rel="noopener">Source: ${moves.source.name}</a></p><div class="home-ranks">${home.ranked('Metro areas rising fastest', moves.metros.slice(0, 6).map(metroRow))}${home.ranked('Metro areas rising slowest or falling', moves.metros.slice(-6).reverse().map(metroRow))}${moves.states.length >= 10 ? home.ranked('States rising fastest', moves.states.slice(0, 6).map(stateRow)) : ''}</div>`) : ''}
${big.length >= 10 ? home.fold('range', 'Where homes cost least and most', '', html`<p class="small muted">Big cities (250,000 people or more) by median home value.</p><div class="home-ranks">${home.ranked('Lowest median value', byValue.slice(0, 6).map(valueRow))}${home.ranked('Highest median value', byValue.slice(-6).reverse().map(valueRow))}</div>`) : ''}
ddiff --git a/server/render/home-blocks.js b/server/render/home-blocks.js
iindex 08b1dda..5d74964 100644
---- a/server/render/home-blocks.js
++++ b/server/render/home-blocks.js
@@@ -22,21 +22,6 @@ const section = (id, title, lede, body) => (render(body).trim()
? html`<section class="sc-sec" aria-labelledby="h-${id}"><h2 id="h-${id}">${title}</h2>${lede ? html`<p class="sc-lede">${lede}</p>` : ''}${body}</section>`
: '');
--/**
-- * The largest cities as a rail of cards (it scrolls sideways and snaps; wide screens wrap it, home.css): the name,
-- * its state, the headline number in the card's corner and the rest on hover. `pick(c)` → { corner, peek };
-- * `href(c)` is where the card goes (the city page by default).
-- */
--function cityRail({ id, title, more, list, pick, href = (c) => c.path }) {
-- return raw(kit.rail({
-- id, title, more, size: 's', wrap: true,
-- items: (list || []).map((c) => {
-- const p = pick(c);
-- return kit.card({ href: href(c), title: c.name, type: 'map', ratio: '4/3', badge: c.state, corner: p.corner || 'not published', peek: p.peek || '', share: false });
-- }),
-- }));
--}
--
/**
* The largest cities as bars, the dearest first (2026-11-01): one look compares them, where a rail of cards drew the
* same map icon twelve times. Each row is a link to its city: the name and state, a bar as long as its rent against
@@@ -84,4 +69,4 @@ const doors = (list) => html`<p class="home-doors">${raw(list.slice(0, 2).map((d
/** Cities with at least `min` people, largest first. */
const bigCities = (min) => cities.cities.filter((c) => c.population >= min).sort((a, b) => b.population - a.population);
--module.exports = { STYLES, stats, section, cityRail, cityBars, fold, ranked, stateChips, siteList, faq, search, doors, bigCities };
++module.exports = { STYLES, stats, section, cityBars, fold, ranked, stateChips, siteList, faq, search, doors, bigCities };
ddiff --git a/test/homes.test.js b/test/homes.test.js
iindex 3db286d..357a179 100644
---- a/test/homes.test.js
++++ b/test/homes.test.js
@@@ -84,7 +84,9 @@ const housingFetchImpl = async (url) => (new URL(String(url)).pathname.endsWith(
// The same front-page shape as Rent's: the search first, the cities as a rail of cards with All N, the parts
// read once folded, the site's places as a launcher.
assert.ok(r.text.includes('<form class="ovk-find" method="get" action="/search" role="search">'), 'the search is the kit\'s pill');
-- assert.ok(/<section class="ovk-rail[^"]*" id="cities"/.test(r.text) && /<a class="ovk-rail-more" href="\/in">All \d+ cities/.test(r.text), 'the cities are a rail with All N');
++ // Bars, dearest first, as on Rent's home (2026-11-01): a rail of cards drew the same map icon for every city.
++ assert.ok(/<ol class="home-bars" aria-labelledby="h-cities">/.test(r.text) && /<a class="home-more" href="\/in">All \d+ cities/.test(r.text), 'the cities are bars with All N');
++ assert.ok(!/ovk-card--map/.test(r.text), 'no placeholder map cards');
assert.ok(/<details class="ovk-fold" id="range">/.test(r.text) && /<details class="ovk-fold" id="states">/.test(r.text) && /<details class="ovk-fold" id="sites">/.test(r.text), 'the ranks, the states and the sites are folds');
assert.ok(/<section class="sc-sec sc-apps-sec" id="places"/.test(r.text) && r.text.includes('href="/records"') && r.text.includes('href="/mortgage"'), 'the places as a launcher');
assert.ok(r.text.includes('Questions people ask') && /"@type":"FAQPage"/.test(r.text) && /"@type":"ItemList"/.test(r.text), 'the questions shown are the FAQPage; the cities are an ItemList');