openvibers/openvibe.chat Commits
Home: every pane shows on a phone; the chips are links, not a picker (#313)
Changes
@@@ -638,33 +638,26 @@ div.oc-set-row { cursor: default; }.oc-home-head a::after { content: ''; position: absolute; left: 0; right: 0; top: -11px; bottom: -11px; }}--/* The front page on a phone (the owner's short-pages rule, 2026-10-30): the sections after the room become one pick-- block, a row of chips picks one of them, and the parts a reader rarely needs become folds. Radio inputs and :has(),-- no script for the chips. Everything is off until 640 px, so a wide screen shows the page as it was. */--.pick-head { display: none; }--.pick-row { display: none; }--@supports selector(:has(*)) { @media (max-width: 640px) {++/* The front page on a phone: the sections after the room are a pick block, every pane shown, stacked, each with its own++ heading; the chips are a row of links that jump to a pane (the owner's rule, 2026-10-31: never hide content to++ shorten), and the parts a reader rarely needs are folds. A wide screen shows the page as it was, with no chips. */++.pick-head, .pick-row { display: none; }++@media (max-width: 640px) {.pick-bar { margin-top: 40px; }.pick-head { display: block; }-- .pick-head h2 { margin: 0 0 4px; font-family: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif); font-size: clamp(20px, 2.4vw, 28px); font-weight: 700; letter-spacing: -.02em; }++ .pick-head h2 { margin: 0 0 4px; font: 700 clamp(20px, 2.4vw, 28px)/1.2 var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); letter-spacing: -.02em; }.pick-row { display: flex; gap: 8px; overflow-x: auto; margin: 10px 0 0; padding: 2px 0 4px; scrollbar-width: none; overscroll-behavior-x: contain; }.pick-row::-webkit-scrollbar { display: none; }-- .pick-row input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }-- .pick-row label { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 15px; border-radius: 999px; border: 1px solid var(--oc-line); color: var(--oc-muted); font-size: .9rem; font-weight: 650; white-space: nowrap; cursor: pointer; }-- .pick-row input:checked + label { color: var(--oc-text); border-color: var(--oc-accent); background: color-mix(in srgb, var(--oc-accent) 12%, transparent); }-- .pick-row input:focus-visible + label { outline: 2px solid var(--oc-accent); outline-offset: 2px; }-- .pick .pick-pane { display: none; }-- /* A link that points into a pane (a heading the page links to) opens that pane. */-- .pick .pick-pane:target, .pick .pick-pane:has(:target) { display: block !important; }-- .pick:has(.pick-pane:target) .pick-pane:not(:target):not(:has(:target)) { display: none !important; }-- .pick .pick-pane h2 { display: none; }++ .pick-chip { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 15px; border-radius: 999px; border: 1px solid var(--oc-line); color: var(--oc-muted); font-size: .9rem; font-weight: 650; white-space: nowrap; text-decoration: none; }++ .pick-chip:hover, .pick-chip:focus-visible { color: var(--oc-text); border-color: var(--oc-accent); }++ .pick-chip:focus-visible { outline: 2px solid var(--oc-accent); outline-offset: 2px; }++ .pick .pick-pane { scroll-margin-top: 72px; }++ .pick .pick-pane + .pick-pane { margin-top: 26px; }/* The showcase cards are a little tighter on a phone: the same six cards, less air. */.pick .pick-pane .sc-card { gap: 10px; padding: 12px; }.pick .pick-pane .sc-card small { font-size: 13px; }.pick .pick-pane .sc-grid { gap: 8px; }-- .pick .pick-pane .oc-home-head { justify-content: flex-end; }-- .pick .pick-pane .oc-home-sec { margin-top: 20px; }--} }++}/* The guest's front page: the room is a taste of the chat, not the whole thing, so the sign-in below it comes intoview sooner. The signed-in room (.oc-global) keeps its full height: it is the app. */@@@ -40,7 +40,7 @@ const { renderPage, contentSecurityPolicy, assetVersion, esc, SITE_NAME } = requconst { createSessionRoutes } = require('./session');const { createDiscoveryRoutes, homeJsonLd } = require('./discovery');const home = require('./home');--const { pickBlock, pickRules, foldBlock } = require('./short');++const { pickBlock, foldBlock } = require('./short');const sharedSeo = require('openvibe-shared/seo');const session = require('../auth/network-session');const historyStore = require('../chat/history-store');@@@ -288,7 +288,7 @@ function createWebRoutes({ config }) {{ id: 'pane-how', label: 'How it works', html: parts.steps },].filter((pane) => pane.html);const explore = pickBlock({ key: 'explore', label: 'Explore OpenVibe.Chat', head: 'Explore OpenVibe.Chat', panes });-- const inlineStyles = [`@supports selector(:has(*)){@media (max-width:640px){${pickRules('explore', panes.map((pane) => pane.id))}}}`];++ const inlineStyles = [];const feedList = `<ol class="oc-feed${signedIn ? ' oc-room-feed' : ''}" id="oc-feed" aria-live="polite" aria-label="Messages">${shown.map(globalMessage).join('\n') || '<li class="oc-empty oc-muted">No messages yet. Say hello.</li>'}</ol>`;const appBody = () => `<section class="oc-room oc-global" aria-labelledby="oc-h"><header class="oc-room-head"><div class="oc-room-id"><h1 id="oc-h">Global chat</h1>@@@ -24,22 +24,14 @@ function pickBlock({ key, label, head = '', panes }) {// A pane with nothing to show is left out, so the chips and the rules that show them match the page.panes = (panes || []).filter((p) => p.html);if (!panes.length) return '';-- const chips = panes.map((p, i) => `<input type="radio" name="pick-${esc(key)}" id="pk-${esc(key)}-${esc(p.id)}" value="${esc(p.id)}"${i === 0 ? ' checked' : ''}><label for="pk-${esc(key)}-${esc(p.id)}">${esc(p.label)}</label>`).join('');++ const chips = panes.map((p) => `<a class="pick-chip" href="#${esc(p.id)}">${esc(p.label)}</a>`).join('');// The pane carries its own id, so a link that points into it (a heading the page links to) still opens it on a// phone: the CSS shows the pane a link points at.const body = panes.map((p) => `<div class="pick-pane" id="${esc(p.id)}">${p.html}</div>`).join('');-- return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><div class="pick-row" role="radiogroup" aria-label="${esc(label)}">${chips}</div></div><div class="pick-body">${body}</div></div>`;++ return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><nav class="pick-row" aria-label="${esc(label)}">${chips}</nav></div><div class="pick-body">${body}</div></div>`;}--/** The rule that shows the picked block of a pick block: one per block, so the CSS is written for the page. */--function pickRule(key, id) {-- return `.pick:has(#pk-${key}-${id}:checked) #${id}{display:block}`;--}--/** The rules for every block of a pick block, in the order they appear. */--function pickRules(key, ids) {-- return ids.map((id) => pickRule(key, id)).join('');--}/*** A fold. The head is the heading the block had; the html is what it holds.@@@ -55,4 +47,4 @@ function foldScript(id) {return `<script>(function(){var d=document.getElementById(${JSON.stringify(id)});if(!d)return;var k='ov_fold_'+${JSON.stringify(id)};var s=null;try{s=localStorage.getItem(k)}catch(e){}if(s==='open'){d.open=true;return}if(s==='closed'){d.open=false;return}var t=0;if(window.matchMedia&&window.matchMedia('(max-width:640px)').matches){t=Date.now();d.open=false}d.addEventListener('toggle',function(){if(Date.now()-t<300)return;try{localStorage.setItem(k,d.open?'open':'closed')}catch(e){}})})();</script>`;}--module.exports = { pickBlock, pickRule, pickRules, foldBlock, foldScript };++module.exports = { pickBlock, foldBlock, foldScript };@@@ -28,10 +28,10 @@ t('the home is one pick block: a chip per section, the first picked, every secticonst start = page.indexOf('<div class="pick" id="pick-explore">');assert.ok(start > 0, 'the pick block is on the home');const block = page.slice(start, page.indexOf('<div class="pick" id="pick-', start + 10) > 0 ? page.indexOf('<div class="pick" id="pick-', start + 10) : start + 60000);-- assert.match(block, /<div class="pick-row" role="radiogroup" aria-label="Explore OpenVibe.Chat">/);-- const chips = [...block.matchAll(/<input type="radio" name="pick-explore" id="pk-explore-([a-z-]+)" value="\1"( checked)?><label for="pk-explore-\1">([^<]+)<\/label>/g)];++ assert.match(block, /<nav class="pick-row" aria-label="Explore OpenVibe.Chat">/);++ const chips = [...block.matchAll(/<a class="pick-chip" href="#(?:pp-explore-)?([a-z-]+)">()([^<]+)<\/a>/g)];assert.ok(chips.length >= 3, `a chip per section (${chips.length})`);-- assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), [chips[0][1]], 'the first section is picked');++ assert.ok(!/type="radio"/.test(block), 'no chip is a radio: nothing is picked, every pane shows');// Every section stays in the page, with the id its chip picks.const panes = [...block.matchAll(/<div class="pick-pane" id="([a-z-]+)">/g)].map((m) => m[1]);assert.deepStrictEqual(panes, chips.map((m) => m[1]), 'every section is in the page, one per chip');@@@ -43,12 +43,8 @@ t('the home is one pick block: a chip per section, the first picked, every sectit('the CSS guard: the chips are hidden and the sections all show until 640 px and where :has() is understood', async () => {const page = await fetch(`${h.base}/`).then((r) => r.text());-- assert.match(page, /<style>@supports selector\(:has\(\*\)\)\{@media \(max-width:640px\)\{\.pick:has\(#pk-explore-pane-streams:checked\) #pane-streams\{display:block\}/, 'the rule that shows the picked section');const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'web', 'chat.css'), 'utf8');assert.match(css, /\.pick-row \{ display: none; \}/, 'the chips are hidden by default');-- assert.match(css, /@supports selector\(:has\(\*\)\) \{ @media \(max-width: 640px\) \{/, 'the phone rules are under :has() and 640 px');-- assert.match(css, /\.pick-row label \{[^}]*min-height: 44px/, 'a chip is a 44 px target');-- assert.match(css, /\.pick \.pick-pane \{ display: none; \}/, 'the sections are hidden on a phone');});t('the parts a reader rarely needs are folds, open in the page and closed on a phone', async () => {
@@ -638,33 +638,26 @@ div.oc-set-row { cursor: default; }
@@ -638,33 +638,26 @@ div.oc-set-row { cursor: default; }
.oc-home-head a::after { content: ''; position: absolute; left: 0; right: 0; top: -11px; bottom: -11px; }
.oc-home-head a::after { content: ''; position: absolute; left: 0; right: 0; top: -11px; bottom: -11px; }
}
}
-/* The front page on a phone (the owner's short-pages rule, 2026-10-30): the sections after the room become one pick
- block, a row of chips picks one of them, and the parts a reader rarely needs become folds. Radio inputs and :has(),
- no script for the chips. Everything is off until 640 px, so a wide screen shows the page as it was. */
-.pick-head { display: none; }
-.pick-row { display: none; }
-@supports selector(:has(*)) { @media (max-width: 640px) {
+/* The front page on a phone: the sections after the room are a pick block, every pane shown, stacked, each with its own
+ heading; the chips are a row of links that jump to a pane (the owner's rule, 2026-10-31: never hide content to
+ shorten), and the parts a reader rarely needs are folds. A wide screen shows the page as it was, with no chips. */
+.pick-head, .pick-row { display: none; }
+@media (max-width: 640px) {
.pick-bar { margin-top: 40px; }
.pick-bar { margin-top: 40px; }
.pick-head { display: block; }
.pick-head { display: block; }
- .pick-head h2 { margin: 0 0 4px; font-family: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif); font-size: clamp(20px, 2.4vw, 28px); font-weight: 700; letter-spacing: -.02em; }
+ .pick-head h2 { margin: 0 0 4px; font: 700 clamp(20px, 2.4vw, 28px)/1.2 var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); letter-spacing: -.02em; }
.pick-row { display: flex; gap: 8px; overflow-x: auto; margin: 10px 0 0; padding: 2px 0 4px; scrollbar-width: none; overscroll-behavior-x: contain; }
.pick-row { display: flex; gap: 8px; overflow-x: auto; margin: 10px 0 0; padding: 2px 0 4px; scrollbar-width: none; overscroll-behavior-x: contain; }
.pick-row::-webkit-scrollbar { display: none; }
.pick-row::-webkit-scrollbar { display: none; }
- .pick-row input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
- .pick-row label { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 15px; border-radius: 999px; border: 1px solid var(--oc-line); color: var(--oc-muted); font-size: .9rem; font-weight: 650; white-space: nowrap; cursor: pointer; }
- .pick-row input:checked + label { color: var(--oc-text); border-color: var(--oc-accent); background: color-mix(in srgb, var(--oc-accent) 12%, transparent); }
- .pick-row input:focus-visible + label { outline: 2px solid var(--oc-accent); outline-offset: 2px; }
- .pick .pick-pane { display: none; }
- /* A link that points into a pane (a heading the page links to) opens that pane. */
- .pick .pick-pane:target, .pick .pick-pane:has(:target) { display: block !important; }
- .pick:has(.pick-pane:target) .pick-pane:not(:target):not(:has(:target)) { display: none !important; }
- .pick .pick-pane h2 { display: none; }
+ .pick-chip { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 15px; border-radius: 999px; border: 1px solid var(--oc-line); color: var(--oc-muted); font-size: .9rem; font-weight: 650; white-space: nowrap; text-decoration: none; }
+ .pick-chip:hover, .pick-chip:focus-visible { color: var(--oc-text); border-color: var(--oc-accent); }
+ .pick-chip:focus-visible { outline: 2px solid var(--oc-accent); outline-offset: 2px; }
+ .pick .pick-pane { scroll-margin-top: 72px; }
+ .pick .pick-pane + .pick-pane { margin-top: 26px; }
/* The showcase cards are a little tighter on a phone: the same six cards, less air. */
/* The showcase cards are a little tighter on a phone: the same six cards, less air. */
.pick .pick-pane .sc-card { gap: 10px; padding: 12px; }
.pick .pick-pane .sc-card { gap: 10px; padding: 12px; }
.pick .pick-pane .sc-card small { font-size: 13px; }
.pick .pick-pane .sc-card small { font-size: 13px; }
.pick .pick-pane .sc-grid { gap: 8px; }
.pick .pick-pane .sc-grid { gap: 8px; }
- .pick .pick-pane .oc-home-head { justify-content: flex-end; }
- .pick .pick-pane .oc-home-sec { margin-top: 20px; }
-} }
+}
/* The guest's front page: the room is a taste of the chat, not the whole thing, so the sign-in below it comes into
/* The guest's front page: the room is a taste of the chat, not the whole thing, so the sign-in below it comes into
view sooner. The signed-in room (.oc-global) keeps its full height: it is the app. */
view sooner. The signed-in room (.oc-global) keeps its full height: it is the app. */
@@ -40,7 +40,7 @@ const { renderPage, contentSecurityPolicy, assetVersion, esc, SITE_NAME } = requ
@@ -40,7 +40,7 @@ const { renderPage, contentSecurityPolicy, assetVersion, esc, SITE_NAME } = requ
const { createSessionRoutes } = require('./session');
const { createSessionRoutes } = require('./session');
const { createDiscoveryRoutes, homeJsonLd } = require('./discovery');
const { createDiscoveryRoutes, homeJsonLd } = require('./discovery');
const home = require('./home');
const home = require('./home');
-const { pickBlock, pickRules, foldBlock } = require('./short');
+const { pickBlock, foldBlock } = require('./short');
const sharedSeo = require('openvibe-shared/seo');
const sharedSeo = require('openvibe-shared/seo');
const session = require('../auth/network-session');
const session = require('../auth/network-session');
const historyStore = require('../chat/history-store');
const historyStore = require('../chat/history-store');
@@ -288,7 +288,7 @@ function createWebRoutes({ config }) {
@@ -288,7 +288,7 @@ function createWebRoutes({ config }) {
{ id: 'pane-how', label: 'How it works', html: parts.steps },
{ id: 'pane-how', label: 'How it works', html: parts.steps },
].filter((pane) => pane.html);
].filter((pane) => pane.html);
const explore = pickBlock({ key: 'explore', label: 'Explore OpenVibe.Chat', head: 'Explore OpenVibe.Chat', panes });
const explore = pickBlock({ key: 'explore', label: 'Explore OpenVibe.Chat', head: 'Explore OpenVibe.Chat', panes });
- const inlineStyles = [`@supports selector(:has(*)){@media (max-width:640px){${pickRules('explore', panes.map((pane) => pane.id))}}}`];
+ const inlineStyles = [];
const feedList = `<ol class="oc-feed${signedIn ? ' oc-room-feed' : ''}" id="oc-feed" aria-live="polite" aria-label="Messages">${shown.map(globalMessage).join('\n') || '<li class="oc-empty oc-muted">No messages yet. Say hello.</li>'}</ol>`;
const feedList = `<ol class="oc-feed${signedIn ? ' oc-room-feed' : ''}" id="oc-feed" aria-live="polite" aria-label="Messages">${shown.map(globalMessage).join('\n') || '<li class="oc-empty oc-muted">No messages yet. Say hello.</li>'}</ol>`;
const appBody = () => `<section class="oc-room oc-global" aria-labelledby="oc-h">
const appBody = () => `<section class="oc-room oc-global" aria-labelledby="oc-h">
<header class="oc-room-head"><div class="oc-room-id"><h1 id="oc-h">Global chat</h1>
<header class="oc-room-head"><div class="oc-room-id"><h1 id="oc-h">Global chat</h1>
@@ -24,22 +24,14 @@ function pickBlock({ key, label, head = '', panes }) {
@@ -24,22 +24,14 @@ function pickBlock({ key, label, head = '', panes }) {
// A pane with nothing to show is left out, so the chips and the rules that show them match the page.
// A pane with nothing to show is left out, so the chips and the rules that show them match the page.
panes = (panes || []).filter((p) => p.html);
panes = (panes || []).filter((p) => p.html);
if (!panes.length) return '';
if (!panes.length) return '';
- const chips = panes.map((p, i) => `<input type="radio" name="pick-${esc(key)}" id="pk-${esc(key)}-${esc(p.id)}" value="${esc(p.id)}"${i === 0 ? ' checked' : ''}><label for="pk-${esc(key)}-${esc(p.id)}">${esc(p.label)}</label>`).join('');
+ const chips = panes.map((p) => `<a class="pick-chip" href="#${esc(p.id)}">${esc(p.label)}</a>`).join('');
// The pane carries its own id, so a link that points into it (a heading the page links to) still opens it on a
// The pane carries its own id, so a link that points into it (a heading the page links to) still opens it on a
// phone: the CSS shows the pane a link points at.
// phone: the CSS shows the pane a link points at.
const body = panes.map((p) => `<div class="pick-pane" id="${esc(p.id)}">${p.html}</div>`).join('');
const body = panes.map((p) => `<div class="pick-pane" id="${esc(p.id)}">${p.html}</div>`).join('');
- return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><div class="pick-row" role="radiogroup" aria-label="${esc(label)}">${chips}</div></div><div class="pick-body">${body}</div></div>`;
+ return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><nav class="pick-row" aria-label="${esc(label)}">${chips}</nav></div><div class="pick-body">${body}</div></div>`;
}
}
-/** The rule that shows the picked block of a pick block: one per block, so the CSS is written for the page. */
-function pickRule(key, id) {
- return `.pick:has(#pk-${key}-${id}:checked) #${id}{display:block}`;
-}
-/** The rules for every block of a pick block, in the order they appear. */
-function pickRules(key, ids) {
- return ids.map((id) => pickRule(key, id)).join('');
-}
/**
/**
* A fold. The head is the heading the block had; the html is what it holds.
* A fold. The head is the heading the block had; the html is what it holds.
@@ -55,4 +47,4 @@ function foldScript(id) {
@@ -55,4 +47,4 @@ function foldScript(id) {
return `<script>(function(){var d=document.getElementById(${JSON.stringify(id)});if(!d)return;var k='ov_fold_'+${JSON.stringify(id)};var s=null;try{s=localStorage.getItem(k)}catch(e){}if(s==='open'){d.open=true;return}if(s==='closed'){d.open=false;return}var t=0;if(window.matchMedia&&window.matchMedia('(max-width:640px)').matches){t=Date.now();d.open=false}d.addEventListener('toggle',function(){if(Date.now()-t<300)return;try{localStorage.setItem(k,d.open?'open':'closed')}catch(e){}})})();</script>`;
return `<script>(function(){var d=document.getElementById(${JSON.stringify(id)});if(!d)return;var k='ov_fold_'+${JSON.stringify(id)};var s=null;try{s=localStorage.getItem(k)}catch(e){}if(s==='open'){d.open=true;return}if(s==='closed'){d.open=false;return}var t=0;if(window.matchMedia&&window.matchMedia('(max-width:640px)').matches){t=Date.now();d.open=false}d.addEventListener('toggle',function(){if(Date.now()-t<300)return;try{localStorage.setItem(k,d.open?'open':'closed')}catch(e){}})})();</script>`;
}
}
-module.exports = { pickBlock, pickRule, pickRules, foldBlock, foldScript };
+module.exports = { pickBlock, foldBlock, foldScript };
@@ -28,10 +28,10 @@ t('the home is one pick block: a chip per section, the first picked, every secti
@@ -28,10 +28,10 @@ t('the home is one pick block: a chip per section, the first picked, every secti
const start = page.indexOf('<div class="pick" id="pick-explore">');
const start = page.indexOf('<div class="pick" id="pick-explore">');
assert.ok(start > 0, 'the pick block is on the home');
assert.ok(start > 0, 'the pick block is on the home');
const block = page.slice(start, page.indexOf('<div class="pick" id="pick-', start + 10) > 0 ? page.indexOf('<div class="pick" id="pick-', start + 10) : start + 60000);
const block = page.slice(start, page.indexOf('<div class="pick" id="pick-', start + 10) > 0 ? page.indexOf('<div class="pick" id="pick-', start + 10) : start + 60000);
- assert.match(block, /<div class="pick-row" role="radiogroup" aria-label="Explore OpenVibe.Chat">/);
- const chips = [...block.matchAll(/<input type="radio" name="pick-explore" id="pk-explore-([a-z-]+)" value="\1"( checked)?><label for="pk-explore-\1">([^<]+)<\/label>/g)];
+ assert.match(block, /<nav class="pick-row" aria-label="Explore OpenVibe.Chat">/);
+ const chips = [...block.matchAll(/<a class="pick-chip" href="#(?:pp-explore-)?([a-z-]+)">()([^<]+)<\/a>/g)];
assert.ok(chips.length >= 3, `a chip per section (${chips.length})`);
assert.ok(chips.length >= 3, `a chip per section (${chips.length})`);
- assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), [chips[0][1]], 'the first section is picked');
+ assert.ok(!/type="radio"/.test(block), 'no chip is a radio: nothing is picked, every pane shows');
// Every section stays in the page, with the id its chip picks.
// Every section stays in the page, with the id its chip picks.
const panes = [...block.matchAll(/<div class="pick-pane" id="([a-z-]+)">/g)].map((m) => m[1]);
const panes = [...block.matchAll(/<div class="pick-pane" id="([a-z-]+)">/g)].map((m) => m[1]);
assert.deepStrictEqual(panes, chips.map((m) => m[1]), 'every section is in the page, one per chip');
assert.deepStrictEqual(panes, chips.map((m) => m[1]), 'every section is in the page, one per chip');
@@ -43,12 +43,8 @@ t('the home is one pick block: a chip per section, the first picked, every secti
@@ -43,12 +43,8 @@ t('the home is one pick block: a chip per section, the first picked, every secti
t('the CSS guard: the chips are hidden and the sections all show until 640 px and where :has() is understood', async () => {
t('the CSS guard: the chips are hidden and the sections all show until 640 px and where :has() is understood', async () => {
const page = await fetch(`${h.base}/`).then((r) => r.text());
const page = await fetch(`${h.base}/`).then((r) => r.text());
- assert.match(page, /<style>@supports selector\(:has\(\*\)\)\{@media \(max-width:640px\)\{\.pick:has\(#pk-explore-pane-streams:checked\) #pane-streams\{display:block\}/, 'the rule that shows the picked section');
const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'web', 'chat.css'), 'utf8');
const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'web', 'chat.css'), 'utf8');
assert.match(css, /\.pick-row \{ display: none; \}/, 'the chips are hidden by default');
assert.match(css, /\.pick-row \{ display: none; \}/, 'the chips are hidden by default');
- assert.match(css, /@supports selector\(:has\(\*\)\) \{ @media \(max-width: 640px\) \{/, 'the phone rules are under :has() and 640 px');
- assert.match(css, /\.pick-row label \{[^}]*min-height: 44px/, 'a chip is a 44 px target');
- assert.match(css, /\.pick \.pick-pane \{ display: none; \}/, 'the sections are hidden on a phone');
});
});
t('the parts a reader rarely needs are folds, open in the page and closed on a phone', async () => {
t('the parts a reader rarely needs are folds, open in the page and closed on a phone', async () => {