openvibers/openvibe.host Commits
Front page: nothing hidden on a phone, counted doors, readable warning words (#249)
Changes
@@@ -189,8 +189,7 @@.hd-table td.hd-go { grid-column: 1 / -1; text-align: left; }.hd-table td.hd-go::before { display: none; }.hd-table td[data-label="Checked"] { grid-column: 1 / -1; }-- /* The list of endings stays short on a phone: the first year, the renewal and five years; the rest is on each ending's page. */-- .hd-list td[data-label="Transfer"], .hd-list td[data-label="Registrars"], .hd-list td[data-label="Checked"] { display: none; }++ /* Every column shows on a phone too (2026-11-02: nothing is hidden to shorten a page); the list is five rows. */}/* Benchmarks (server/render/benchmarks.js). */@@@ -1,7 +1,7 @@/* OpenVibe.Host dashboard. Colours follow the shared theme tokens when theme-loader.js has run. */:root { --hs-bg: var(--bg-primary, #0a0f1c); --hs-fg: var(--text-primary, #e6edf7); --hs-muted: var(--text-secondary, #9aa7bd);--hs-card: var(--bg-secondary, #111a2c); --hs-line: var(--border, rgba(255,255,255,.12)); --hs-accent: var(--accent, #60a5fa);-- --hs-ok: var(--success-text, #34d399); --hs-warn: var(--warning-text, #fbbf24); --hs-bad: var(--danger-text, #f87171); }++ --hs-ok: var(--success-text, #34d399); --hs-warn: color-mix(in srgb, var(--warning-text, #fbbf24) 80%, var(--text-primary, #e6edf7)); --hs-bad: var(--danger-text, #f87171); }/* Status words and lines use the theme's readable status colours; the fixed light green, amber and red were for darkthemes only (axe on Daylight: 1.8:1, 2026-11-01). Buttons wear the theme's strong accent and its own text colour. */body { margin: 0; background: var(--hs-bg); color: var(--hs-fg); font: 15px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif; }@@@ -36,10 +36,10 @@ ul.domains { padding-left: 1.2rem; } ul.domains li { margin-bottom: 1rem; }.intro { max-width: 44rem; }@media (max-width: 40rem) { table { display: block; overflow-x: auto; } .account-bar { text-align: left; } }--/* Phones (2026-10-29): a taste and a door. The front page's plan and domain lists show four rows each (every plan-- and every ending are a link away), and the numbers are one strip that scrolls sideways. */++/* Phones (2026-10-29): a taste and a door. The front page renders five rows per list at every width (2026-11-02:++ rows are never hidden on a phone; every plan and every ending are a counted link away), and the numbers are one++ strip that scrolls sideways. */@media (max-width: 599px) {-- .hh-short .hc-table tbody tr:nth-child(n+5) { display: none; }.hh-stats { display: flex; overflow-x: auto; scrollbar-width: none; gap: 8px; margin-left: -16px; margin-right: -16px; padding: 0 16px 2px; }.hh-stats::-webkit-scrollbar { display: none; }.hh-stats li { flex: 0 0 auto; min-width: 128px; }@@@ -477,8 +477,8 @@ function front({ plans, providers, promos: live, stats, offers: recent = [], domactions: [{ label: 'Compare plans', href: '/catalog', primary: true }, { label: 'Domain prices', href: '/catalog/domains' }],note: stats.plans ? `${stats.plans} plans from ${stats.providers} provider${stats.providers === 1 ? '' : 's'} so far. More providers join as their sources are checked.` : 'The catalog is filling up: providers join as their official sources are checked and enabled.',aside: aside ? { html: aside } : undefined,-- }) + extra.stats + changeRail + `<section class="sc-sec hh-short" aria-labelledby="hh-cheapest"><h2 id="hh-cheapest">The lowest monthly prices</h2>${planTable(plans.slice(0, 8), { caption: 'Lowest price first', now })}--<p class="hc-more">${link('/catalog', 'Filter and sort every plan →', 'button')}</p></section>`++ }) + extra.stats + changeRail + `<section class="sc-sec hh-short" aria-labelledby="hh-cheapest"><h2 id="hh-cheapest">The lowest monthly prices</h2>${planTable(plans.slice(0, 5), { caption: 'Lowest price first', now })}++<p class="hc-more">${link('/catalog', stats.plans > 5 ? `Filter and sort all ${Number(stats.plans).toLocaleString('en')} plans →` : 'Filter and sort every plan →', 'button')}</p></section>`+ extra.sizes + extra.value + extra.providers+ domainSection+ '<section class="sc-sec" id="why" aria-labelledby="hh-why"><details class="hh-fold"><summary><h2 id="hh-why">Why this comparison is different</h2><span>sourced, dated, ranked by you</span></summary>'@@@ -162,9 +162,9 @@ function frontSection({ popular = [], cheap = [], stats }) {return `<section class="sc-sec hd-front" aria-labelledby="hd-front-h"><h2 id="hd-front-h">Domain names, compared too</h2><p class="hc-dim">${stats.tlds ? `${stats.tlds.toLocaleString('en')} endings${registrars > 1 ? ` from ${registrars} registrars (${stats.registrars.map(esc).join(', ')})` : ''}, with the first year, the renewal and five years side by side. ` : ''}Check a name at its registry, then register it where it costs least.</p>${checkForm({})}--<div class="hh-short">${tldTable(popular.slice(0, 6), { caption: 'Popular endings: the cheapest price at any registrar' })}</div>--${cheap.length ? '<div class="hh-short">' + tldTable(cheap.slice(0, 6), { caption: 'The cheapest first year right now: check what it renews at' }) + '</div>' : ''}--${stats.tlds ? `<p class="hc-more">${link('/catalog/domains', 'Every ending, by price →', 'button')}</p>` : ''}</section>`;++<div class="hh-short">${tldTable(popular.slice(0, 5), { caption: 'Popular endings: the cheapest price at any registrar' })}</div>++${cheap.length ? '<div class="hh-short">' + tldTable(cheap.slice(0, 5), { caption: 'The cheapest first year right now: check what it renews at' }) + '</div>' : ''}++${stats.tlds ? `<p class="hc-more">${link('/catalog/domains', `All ${Number(stats.tlds).toLocaleString('en')} endings, by price →`, 'button')}</p>` : ''}</section>`;}const jsonLd = {nnew file mode 100644@@@ -0,0 +1,22 @@++'use strict';++++/**++ * Nothing is hidden to shorten a page (owner, 2026-10-31): the front page's short lists render the same rows at every++ * width and end in a counted link, and the endings list keeps every column on a phone.++ */++const assert = require('assert');++const fs = require('fs');++const path = require('path');++++const css = (f) => fs.readFileSync(path.join(__dirname, '..', 'public', 'css', f), 'utf8');++++assert.ok(!/tr:nth-child\(n\+\d+\)\s*\{\s*display:\s*none/.test(css('host.css')), 'no row of a short list is hidden on a phone');++assert.ok(!/\.hd-list td\[data-label=[^\]]+\][^{]*\{\s*display:\s*none/.test(css('catalog.css')), 'no column of the endings list is hidden on a phone');++++const catalog = fs.readFileSync(path.join(__dirname, '..', 'server', 'render', 'catalog.js'), 'utf8');++assert.ok(catalog.includes('planTable(plans.slice(0, 5)'), 'the front page renders the five rows it shows');++assert.ok(catalog.includes('Filter and sort all ${'), 'the door to every plan is counted');++const domains = fs.readFileSync(path.join(__dirname, '..', 'server', 'render', 'domains.js'), 'utf8');++assert.ok(domains.includes('All ${Number(stats.tlds)'), 'the door to every ending is counted');++++console.log('front-unhidden: all passed');@@@ -8,7 +8,8 @@ const path = require('path');const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'host.css'), 'utf8');assert.match(css, /--hs-ok: var\(--success-text, #34d399\)/);--assert.match(css, /--hs-warn: var\(--warning-text, #fbbf24\)/);++// Mixed toward the text colour so a warning word also reads on a tinted row (Daylight, 2026-11-02).++assert.match(css, /--hs-warn: color-mix\(in srgb, var\(--warning-text, #fbbf24\) 80%, var\(--text-primary, #e6edf7\)\)/);assert.match(css, /--hs-bad: var\(--danger-text, #f87171\)/);assert.match(css, /--hs-line: var\(--border, rgba\(255,255,255,\.12\)\)/);assert.match(css, /button, \.button \{[^}]*background: var\(--accent-strong, var\(--hs-accent\)\); color: var\(--on-accent-strong, #0a0f1c\)/);
@@ -189,8 +189,7 @@
@@ -189,8 +189,7 @@
.hd-table td.hd-go { grid-column: 1 / -1; text-align: left; }
.hd-table td.hd-go { grid-column: 1 / -1; text-align: left; }
.hd-table td.hd-go::before { display: none; }
.hd-table td.hd-go::before { display: none; }
.hd-table td[data-label="Checked"] { grid-column: 1 / -1; }
.hd-table td[data-label="Checked"] { grid-column: 1 / -1; }
- /* The list of endings stays short on a phone: the first year, the renewal and five years; the rest is on each ending's page. */
- .hd-list td[data-label="Transfer"], .hd-list td[data-label="Registrars"], .hd-list td[data-label="Checked"] { display: none; }
+ /* Every column shows on a phone too (2026-11-02: nothing is hidden to shorten a page); the list is five rows. */
}
}
/* Benchmarks (server/render/benchmarks.js). */
/* Benchmarks (server/render/benchmarks.js). */
@@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
/* OpenVibe.Host dashboard. Colours follow the shared theme tokens when theme-loader.js has run. */
/* OpenVibe.Host dashboard. Colours follow the shared theme tokens when theme-loader.js has run. */
:root { --hs-bg: var(--bg-primary, #0a0f1c); --hs-fg: var(--text-primary, #e6edf7); --hs-muted: var(--text-secondary, #9aa7bd);
:root { --hs-bg: var(--bg-primary, #0a0f1c); --hs-fg: var(--text-primary, #e6edf7); --hs-muted: var(--text-secondary, #9aa7bd);
--hs-card: var(--bg-secondary, #111a2c); --hs-line: var(--border, rgba(255,255,255,.12)); --hs-accent: var(--accent, #60a5fa);
--hs-card: var(--bg-secondary, #111a2c); --hs-line: var(--border, rgba(255,255,255,.12)); --hs-accent: var(--accent, #60a5fa);
- --hs-ok: var(--success-text, #34d399); --hs-warn: var(--warning-text, #fbbf24); --hs-bad: var(--danger-text, #f87171); }
+ --hs-ok: var(--success-text, #34d399); --hs-warn: color-mix(in srgb, var(--warning-text, #fbbf24) 80%, var(--text-primary, #e6edf7)); --hs-bad: var(--danger-text, #f87171); }
/* Status words and lines use the theme's readable status colours; the fixed light green, amber and red were for dark
/* Status words and lines use the theme's readable status colours; the fixed light green, amber and red were for dark
themes only (axe on Daylight: 1.8:1, 2026-11-01). Buttons wear the theme's strong accent and its own text colour. */
themes only (axe on Daylight: 1.8:1, 2026-11-01). Buttons wear the theme's strong accent and its own text colour. */
body { margin: 0; background: var(--hs-bg); color: var(--hs-fg); font: 15px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif; }
body { margin: 0; background: var(--hs-bg); color: var(--hs-fg); font: 15px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif; }
@@ -36,10 +36,10 @@ ul.domains { padding-left: 1.2rem; } ul.domains li { margin-bottom: 1rem; }
@@ -36,10 +36,10 @@ ul.domains { padding-left: 1.2rem; } ul.domains li { margin-bottom: 1rem; }
.intro { max-width: 44rem; }
.intro { max-width: 44rem; }
@media (max-width: 40rem) { table { display: block; overflow-x: auto; } .account-bar { text-align: left; } }
@media (max-width: 40rem) { table { display: block; overflow-x: auto; } .account-bar { text-align: left; } }
-/* Phones (2026-10-29): a taste and a door. The front page's plan and domain lists show four rows each (every plan
- and every ending are a link away), and the numbers are one strip that scrolls sideways. */
+/* Phones (2026-10-29): a taste and a door. The front page renders five rows per list at every width (2026-11-02:
+ rows are never hidden on a phone; every plan and every ending are a counted link away), and the numbers are one
+ strip that scrolls sideways. */
@media (max-width: 599px) {
@media (max-width: 599px) {
- .hh-short .hc-table tbody tr:nth-child(n+5) { display: none; }
.hh-stats { display: flex; overflow-x: auto; scrollbar-width: none; gap: 8px; margin-left: -16px; margin-right: -16px; padding: 0 16px 2px; }
.hh-stats { display: flex; overflow-x: auto; scrollbar-width: none; gap: 8px; margin-left: -16px; margin-right: -16px; padding: 0 16px 2px; }
.hh-stats::-webkit-scrollbar { display: none; }
.hh-stats::-webkit-scrollbar { display: none; }
.hh-stats li { flex: 0 0 auto; min-width: 128px; }
.hh-stats li { flex: 0 0 auto; min-width: 128px; }
@@ -477,8 +477,8 @@ function front({ plans, providers, promos: live, stats, offers: recent = [], dom
@@ -477,8 +477,8 @@ function front({ plans, providers, promos: live, stats, offers: recent = [], dom
actions: [{ label: 'Compare plans', href: '/catalog', primary: true }, { label: 'Domain prices', href: '/catalog/domains' }],
actions: [{ label: 'Compare plans', href: '/catalog', primary: true }, { label: 'Domain prices', href: '/catalog/domains' }],
note: stats.plans ? `${stats.plans} plans from ${stats.providers} provider${stats.providers === 1 ? '' : 's'} so far. More providers join as their sources are checked.` : 'The catalog is filling up: providers join as their official sources are checked and enabled.',
note: stats.plans ? `${stats.plans} plans from ${stats.providers} provider${stats.providers === 1 ? '' : 's'} so far. More providers join as their sources are checked.` : 'The catalog is filling up: providers join as their official sources are checked and enabled.',
aside: aside ? { html: aside } : undefined,
aside: aside ? { html: aside } : undefined,
- }) + extra.stats + changeRail + `<section class="sc-sec hh-short" aria-labelledby="hh-cheapest"><h2 id="hh-cheapest">The lowest monthly prices</h2>${planTable(plans.slice(0, 8), { caption: 'Lowest price first', now })}
-<p class="hc-more">${link('/catalog', 'Filter and sort every plan →', 'button')}</p></section>`
+ }) + extra.stats + changeRail + `<section class="sc-sec hh-short" aria-labelledby="hh-cheapest"><h2 id="hh-cheapest">The lowest monthly prices</h2>${planTable(plans.slice(0, 5), { caption: 'Lowest price first', now })}
+<p class="hc-more">${link('/catalog', stats.plans > 5 ? `Filter and sort all ${Number(stats.plans).toLocaleString('en')} plans →` : 'Filter and sort every plan →', 'button')}</p></section>`
+ extra.sizes + extra.value + extra.providers
+ extra.sizes + extra.value + extra.providers
+ domainSection
+ domainSection
+ '<section class="sc-sec" id="why" aria-labelledby="hh-why"><details class="hh-fold"><summary><h2 id="hh-why">Why this comparison is different</h2><span>sourced, dated, ranked by you</span></summary>'
+ '<section class="sc-sec" id="why" aria-labelledby="hh-why"><details class="hh-fold"><summary><h2 id="hh-why">Why this comparison is different</h2><span>sourced, dated, ranked by you</span></summary>'
@@ -162,9 +162,9 @@ function frontSection({ popular = [], cheap = [], stats }) {
@@ -162,9 +162,9 @@ function frontSection({ popular = [], cheap = [], stats }) {
return `<section class="sc-sec hd-front" aria-labelledby="hd-front-h"><h2 id="hd-front-h">Domain names, compared too</h2>
return `<section class="sc-sec hd-front" aria-labelledby="hd-front-h"><h2 id="hd-front-h">Domain names, compared too</h2>
<p class="hc-dim">${stats.tlds ? `${stats.tlds.toLocaleString('en')} endings${registrars > 1 ? ` from ${registrars} registrars (${stats.registrars.map(esc).join(', ')})` : ''}, with the first year, the renewal and five years side by side. ` : ''}Check a name at its registry, then register it where it costs least.</p>
<p class="hc-dim">${stats.tlds ? `${stats.tlds.toLocaleString('en')} endings${registrars > 1 ? ` from ${registrars} registrars (${stats.registrars.map(esc).join(', ')})` : ''}, with the first year, the renewal and five years side by side. ` : ''}Check a name at its registry, then register it where it costs least.</p>
${checkForm({})}
${checkForm({})}
-<div class="hh-short">${tldTable(popular.slice(0, 6), { caption: 'Popular endings: the cheapest price at any registrar' })}</div>
-${cheap.length ? '<div class="hh-short">' + tldTable(cheap.slice(0, 6), { caption: 'The cheapest first year right now: check what it renews at' }) + '</div>' : ''}
-${stats.tlds ? `<p class="hc-more">${link('/catalog/domains', 'Every ending, by price →', 'button')}</p>` : ''}</section>`;
+<div class="hh-short">${tldTable(popular.slice(0, 5), { caption: 'Popular endings: the cheapest price at any registrar' })}</div>
+${cheap.length ? '<div class="hh-short">' + tldTable(cheap.slice(0, 5), { caption: 'The cheapest first year right now: check what it renews at' }) + '</div>' : ''}
+${stats.tlds ? `<p class="hc-more">${link('/catalog/domains', `All ${Number(stats.tlds).toLocaleString('en')} endings, by price →`, 'button')}</p>` : ''}</section>`;
}
}
const jsonLd = {
const jsonLd = {
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+'use strict';
+
+/**
+ * Nothing is hidden to shorten a page (owner, 2026-10-31): the front page's short lists render the same rows at every
+ * width and end in a counted link, and the endings list keeps every column on a phone.
+ */
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+
+const css = (f) => fs.readFileSync(path.join(__dirname, '..', 'public', 'css', f), 'utf8');
+
+assert.ok(!/tr:nth-child\(n\+\d+\)\s*\{\s*display:\s*none/.test(css('host.css')), 'no row of a short list is hidden on a phone');
+assert.ok(!/\.hd-list td\[data-label=[^\]]+\][^{]*\{\s*display:\s*none/.test(css('catalog.css')), 'no column of the endings list is hidden on a phone');
+
+const catalog = fs.readFileSync(path.join(__dirname, '..', 'server', 'render', 'catalog.js'), 'utf8');
+assert.ok(catalog.includes('planTable(plans.slice(0, 5)'), 'the front page renders the five rows it shows');
+assert.ok(catalog.includes('Filter and sort all ${'), 'the door to every plan is counted');
+const domains = fs.readFileSync(path.join(__dirname, '..', 'server', 'render', 'domains.js'), 'utf8');
+assert.ok(domains.includes('All ${Number(stats.tlds)'), 'the door to every ending is counted');
+
+console.log('front-unhidden: all passed');
@@ -8,7 +8,8 @@ const path = require('path');
@@ -8,7 +8,8 @@ const path = require('path');
const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'host.css'), 'utf8');
const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'host.css'), 'utf8');
assert.match(css, /--hs-ok: var\(--success-text, #34d399\)/);
assert.match(css, /--hs-ok: var\(--success-text, #34d399\)/);
-assert.match(css, /--hs-warn: var\(--warning-text, #fbbf24\)/);
+// Mixed toward the text colour so a warning word also reads on a tinted row (Daylight, 2026-11-02).
+assert.match(css, /--hs-warn: color-mix\(in srgb, var\(--warning-text, #fbbf24\) 80%, var\(--text-primary, #e6edf7\)\)/);
assert.match(css, /--hs-bad: var\(--danger-text, #f87171\)/);
assert.match(css, /--hs-bad: var\(--danger-text, #f87171\)/);
assert.match(css, /--hs-line: var\(--border, rgba\(255,255,255,\.12\)\)/);
assert.match(css, /--hs-line: var\(--border, rgba\(255,255,255,\.12\)\)/);
assert.match(css, /button, \.button \{[^}]*background: var\(--accent-strong, var\(--hs-accent\)\); color: var\(--on-accent-strong, #0a0f1c\)/);
assert.match(css, /button, \.button \{[^}]*background: var\(--accent-strong, var\(--hs-accent\)\); color: var\(--on-accent-strong, #0a0f1c\)/);