openvibers/openvibe.media Commits
Home: every pane shows on a phone; the chips are links, not a picker (#271)
Changes
@@@ -24,7 +24,7 @@ const cache = require('openvibe-shared/cache-policy');const frame = require('openvibe-shared/frame');const showcase = require('openvibe-shared/showcase');const kit = require('openvibe-shared/kit');--const { pickBlock, pickRules, foldBlock } = require('./short');++const { pickBlock, foldBlock } = require('./short');const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');const abs = (u) => (!u ? null : (/^https?:\/\//i.test(u) ? u : `${config.publicUrl}${u.startsWith('/') ? '' : '/'}${u}`));@@@ -269,25 +269,19 @@ nav.tabs a .n{opacity:.75;font-size:.78rem;margin-left:.3rem}nav.tabs a.on .n{op.faq{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:4px 16px;margin:8px 0}.faq[open]{padding-bottom:12px}.faq summary{cursor:pointer;font-weight:600;padding:8px 0}.faq p{margin:4px 0 0;color:var(--muted)}@media (pointer: coarse){.faq summary{min-height:44px}}--/* Pick (the owner's short-pages rule, 2026-10-30): on a phone the home's blocks of the same kind are one at a time,-- picked with a row of chips. Radio inputs and :has(), no script: the chips and the one-block rule live under 640 px-- and only where :has() is understood, so a wider screen, or an older browser, shows every block and no chips. */--.pick-head{display:none}--.pick-row{display:none}++/* Pick: on a phone every pane is shown, stacked; the chips are a row of links that jump to a pane (the owner's rule,++ 2026-10-31: never hide content to shorten). A wide screen shows the panes as before, with no chips. */++.pick-head,.pick-row{display:none}.pick-pane{min-width:0}--@supports selector(:has(*)){@media (max-width:640px){-- .pick-head{display:block}-- .pick-head h2{margin:0 0 4px;font-size:1.25rem}++@media (max-width:640px){.pick-row{display:flex;gap:8px;overflow-x:auto;margin:10px -16px 0;padding:2px 16px 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(--line);color:var(--muted);font-size:.9rem;font-weight:700;white-space:nowrap;cursor:pointer}-- .pick-row input:checked+label{color:var(--text);border-color:var(--acc);background:color-mix(in srgb,var(--acc) 12%,transparent)}-- .pick-row input:focus-visible+label{outline:2px solid var(--acc);outline-offset:2px}-- .pick-pane{display:none}-- /* The section's own heading stays (its text comes before it otherwise); the pick block's second heading gives way. */-- .md-pick-sec .pick-head 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(--line);color:var(--muted);font-size:.9rem;font-weight:700;white-space:nowrap;text-decoration:none}++ .pick-chip:hover,.pick-chip:focus-visible{color:var(--text);border-color:var(--acc)}++ .pick-chip:focus-visible{outline:2px solid var(--acc);outline-offset:2px}++ .pick .pick-pane{scroll-margin-top:72px}++ .pick .pick-pane+.pick-pane{margin-top:18px}++}/* The fold (the owner's short-pages rule): closed on a phone, and where there is room it looks like the section itreplaces: a heading and its content, with no card around it. */.md-fold-block{margin:32px 0}@@@ -361,7 +355,6 @@ const { url } = await objects.signedUrl(obj.id, { ttl: 300 });` },label: c.label,html: `<figure><figcaption>${esc(c.label)}</figcaption><pre tabindex="0" aria-label="${esc(c.label)}"><code class="language-${esc(c.lang)}">${esc(c.code)}</code></pre></figure>`,}));-- if (rules) rules.push(pickRules('api', panes.map((p) => p.id)));const api = `<section class="sc-sec md-pick-sec" id="api" aria-labelledby="api-h"><h2 id="api-h">From your app</h2><p class="sc-lede">Public media needs no key. Your own objects take a project token or a Zone key.</p>${pickBlock({ key: 'api', label: 'The ways to call Media', head: 'From your app', bodyClass: 'sc-code', panes })}@@@ -22,16 +22,13 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</*/function pickBlock({ key, label, head = '', headId = '', bodyClass = '', panes }) {if (!panes || !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="#pp-${esc(key)}-${esc(p.id)}">${esc(p.label)}</a>`).join('');const body = panes.map((p) => `<div class="pick-pane" id="pp-${esc(key)}-${esc(p.id)}">${p.html}</div>`).join('');const heading = `<h2${headId ? ` id="${esc(headId)}"` : ''}>${esc(head || label)}</h2>`;-- return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head">${heading}</div><div class="pick-row" role="radiogroup" aria-label="${esc(label)}">${chips}</div></div><div class="pick-body${bodyClass ? ` ${bodyClass}` : ''}">${body}</div></div>`;++ return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head">${heading}</div><nav class="pick-row" aria-label="${esc(label)}">${chips}</nav></div><div class="pick-body${bodyClass ? ` ${bodyClass}` : ''}">${body}</div></div>`;}/** The rules for every pane of a pick block, in the order they appear: one rule per pane, so the CSS is written for the page. */--function pickRules(key, ids) {-- return ids.map((id) => `.pick:has(#pk-${key}-${id}:checked) #pp-${key}-${id}{display:block}`).join('');--}/*** A fold. The head is the heading the block had; the html is what it holds.@@@ -47,4 +44,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, pickRules, foldBlock, foldScript };++module.exports = { pickBlock, foldBlock, foldScript };@@@ -1,6 +1,5 @@'use strict';// The home on a phone (the owner's short-pages rule, 2026-10-30): the code samples are one pick block, a row of chips--// picks one of them, and the parts a reader rarely needs are folds. Radio inputs and :has(), no script for the chips;// every block stays in the page for crawlers; a wider screen shows them all and no chips.const assert = require('assert');const fs = require('fs');@@@ -37,17 +36,15 @@ const path = require('path');// The code samples: one pick block, a chip per sample, the first one picked.assert.ok(text.includes('<div class="pick" id="pick-api">'), 'the code samples are one pick block');-- assert.match(text, /<div class="pick-row" role="radiogroup" aria-label="The ways to call Media">/);-- const chips = [...text.matchAll(/<input type="radio" name="pick-api" id="pk-api-([a-z0-9-]+)" value="\1"( checked)?><label for="pk-api-\1">([^<]+)<\/label>/g)];++ assert.match(text, /<nav class="pick-row" aria-label="The ways to call Media">/);++ const chips = [...text.matchAll(/<a class="pick-chip" href="#(?:pp-api-)?([a-z0-9-]+)">()([^<]+)<\/a>/g)];assert.deepStrictEqual(chips.map((m) => m[1]), ['thumbnail', 'node', 's3'], 'a chip per sample');-- assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), ['thumbnail'], 'the first sample is picked');++ assert.ok(!/type="radio"/.test(text), 'no chip is a radio: nothing is picked, every pane shows');assert.strictEqual((text.match(/class="pick-pane"/g) || []).length, chips.length, 'one pane per chip');// Every sample stays in the page for the crawlers.for (const label of ['Thumbnail', 'Node (openvibe-sdk)', 'S3 (any tool)']) {assert.ok(text.includes(`<figcaption>${label}</figcaption>`), `${label} is in the page`);}-- // The rule that shows the picked sample is written for this page.-- assert.ok(text.includes('.pick:has(#pk-api-thumbnail:checked) #pp-api-thumbnail{display:block}'), 'the picked sample shows');// The folds: open in the page, closed on a phone, and their content stays in the page.for (const id of ['what', 'limits', 'questions', 'shipped']) {@@@ -65,12 +62,10 @@ const path = require('path');assert.ok(text.includes('class="sc-sec sc-media"'), 'the newest media rail is still there');assert.ok(text.includes('id="api"'), 'the code samples are still there, under their own anchor');-- // The CSS guard: the chips are hidden and the blocks all show until 640 px and where :has() is understood.const css = fs.readFileSync(path.join(__dirname, '..', 'server', 'public', 'browse.js'), '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-pane\{display:none\}/, 'the blocks are hidden on a phone');++ assert.match(css, /\.pick-head,\.pick-row\{display:none\}/, 'no chips where there is room');++ assert.doesNotMatch(css, /\.pick-pane\{display:none\}/, 'no pane is ever hidden (the owner, 2026-10-31)');++ assert.match(css, /\.pick-chip\{/, 'the chips are links');console.log('home short: all checks passed');} finally {
@@ -24,7 +24,7 @@ const cache = require('openvibe-shared/cache-policy');
@@ -24,7 +24,7 @@ const cache = require('openvibe-shared/cache-policy');
const frame = require('openvibe-shared/frame');
const frame = require('openvibe-shared/frame');
const showcase = require('openvibe-shared/showcase');
const showcase = require('openvibe-shared/showcase');
const kit = require('openvibe-shared/kit');
const kit = require('openvibe-shared/kit');
-const { pickBlock, pickRules, foldBlock } = require('./short');
+const { pickBlock, foldBlock } = require('./short');
const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
const abs = (u) => (!u ? null : (/^https?:\/\//i.test(u) ? u : `${config.publicUrl}${u.startsWith('/') ? '' : '/'}${u}`));
const abs = (u) => (!u ? null : (/^https?:\/\//i.test(u) ? u : `${config.publicUrl}${u.startsWith('/') ? '' : '/'}${u}`));
@@ -269,25 +269,19 @@ nav.tabs a .n{opacity:.75;font-size:.78rem;margin-left:.3rem}nav.tabs a.on .n{op
@@ -269,25 +269,19 @@ nav.tabs a .n{opacity:.75;font-size:.78rem;margin-left:.3rem}nav.tabs a.on .n{op
.faq{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:4px 16px;margin:8px 0}
.faq{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:4px 16px;margin:8px 0}
.faq[open]{padding-bottom:12px}.faq summary{cursor:pointer;font-weight:600;padding:8px 0}.faq p{margin:4px 0 0;color:var(--muted)}
.faq[open]{padding-bottom:12px}.faq summary{cursor:pointer;font-weight:600;padding:8px 0}.faq p{margin:4px 0 0;color:var(--muted)}
@media (pointer: coarse){.faq summary{min-height:44px}}
@media (pointer: coarse){.faq summary{min-height:44px}}
-/* Pick (the owner's short-pages rule, 2026-10-30): on a phone the home's blocks of the same kind are one at a time,
- picked with a row of chips. Radio inputs and :has(), no script: the chips and the one-block rule live under 640 px
- and only where :has() is understood, so a wider screen, or an older browser, shows every block and no chips. */
-.pick-head{display:none}
-.pick-row{display:none}
+/* Pick: on a phone every pane is shown, stacked; the chips are a row of links that jump to a pane (the owner's rule,
+ 2026-10-31: never hide content to shorten). A wide screen shows the panes as before, with no chips. */
+.pick-head,.pick-row{display:none}
.pick-pane{min-width:0}
.pick-pane{min-width:0}
-@supports selector(:has(*)){@media (max-width:640px){
- .pick-head{display:block}
- .pick-head h2{margin:0 0 4px;font-size:1.25rem}
+@media (max-width:640px){
.pick-row{display:flex;gap:8px;overflow-x:auto;margin:10px -16px 0;padding:2px 16px 4px;scrollbar-width:none;overscroll-behavior-x:contain}
.pick-row{display:flex;gap:8px;overflow-x:auto;margin:10px -16px 0;padding:2px 16px 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(--line);color:var(--muted);font-size:.9rem;font-weight:700;white-space:nowrap;cursor:pointer}
- .pick-row input:checked+label{color:var(--text);border-color:var(--acc);background:color-mix(in srgb,var(--acc) 12%,transparent)}
- .pick-row input:focus-visible+label{outline:2px solid var(--acc);outline-offset:2px}
- .pick-pane{display:none}
- /* The section's own heading stays (its text comes before it otherwise); the pick block's second heading gives way. */
- .md-pick-sec .pick-head 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(--line);color:var(--muted);font-size:.9rem;font-weight:700;white-space:nowrap;text-decoration:none}
+ .pick-chip:hover,.pick-chip:focus-visible{color:var(--text);border-color:var(--acc)}
+ .pick-chip:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
+ .pick .pick-pane{scroll-margin-top:72px}
+ .pick .pick-pane+.pick-pane{margin-top:18px}
+}
/* The fold (the owner's short-pages rule): closed on a phone, and where there is room it looks like the section it
/* The fold (the owner's short-pages rule): closed on a phone, and where there is room it looks like the section it
replaces: a heading and its content, with no card around it. */
replaces: a heading and its content, with no card around it. */
.md-fold-block{margin:32px 0}
.md-fold-block{margin:32px 0}
@@ -361,7 +355,6 @@ const { url } = await objects.signedUrl(obj.id, { ttl: 300 });` },
@@ -361,7 +355,6 @@ const { url } = await objects.signedUrl(obj.id, { ttl: 300 });` },
label: c.label,
label: c.label,
html: `<figure><figcaption>${esc(c.label)}</figcaption><pre tabindex="0" aria-label="${esc(c.label)}"><code class="language-${esc(c.lang)}">${esc(c.code)}</code></pre></figure>`,
html: `<figure><figcaption>${esc(c.label)}</figcaption><pre tabindex="0" aria-label="${esc(c.label)}"><code class="language-${esc(c.lang)}">${esc(c.code)}</code></pre></figure>`,
}));
}));
- if (rules) rules.push(pickRules('api', panes.map((p) => p.id)));
const api = `<section class="sc-sec md-pick-sec" id="api" aria-labelledby="api-h"><h2 id="api-h">From your app</h2>
const api = `<section class="sc-sec md-pick-sec" id="api" aria-labelledby="api-h"><h2 id="api-h">From your app</h2>
<p class="sc-lede">Public media needs no key. Your own objects take a project token or a Zone key.</p>
<p class="sc-lede">Public media needs no key. Your own objects take a project token or a Zone key.</p>
${pickBlock({ key: 'api', label: 'The ways to call Media', head: 'From your app', bodyClass: 'sc-code', panes })}
${pickBlock({ key: 'api', label: 'The ways to call Media', head: 'From your app', bodyClass: 'sc-code', panes })}
@@ -22,16 +22,13 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</
@@ -22,16 +22,13 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</
*/
*/
function pickBlock({ key, label, head = '', headId = '', bodyClass = '', panes }) {
function pickBlock({ key, label, head = '', headId = '', bodyClass = '', panes }) {
if (!panes || !panes.length) return '';
if (!panes || !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="#pp-${esc(key)}-${esc(p.id)}">${esc(p.label)}</a>`).join('');
const body = panes.map((p) => `<div class="pick-pane" id="pp-${esc(key)}-${esc(p.id)}">${p.html}</div>`).join('');
const body = panes.map((p) => `<div class="pick-pane" id="pp-${esc(key)}-${esc(p.id)}">${p.html}</div>`).join('');
const heading = `<h2${headId ? ` id="${esc(headId)}"` : ''}>${esc(head || label)}</h2>`;
const heading = `<h2${headId ? ` id="${esc(headId)}"` : ''}>${esc(head || label)}</h2>`;
- return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head">${heading}</div><div class="pick-row" role="radiogroup" aria-label="${esc(label)}">${chips}</div></div><div class="pick-body${bodyClass ? ` ${bodyClass}` : ''}">${body}</div></div>`;
+ return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head">${heading}</div><nav class="pick-row" aria-label="${esc(label)}">${chips}</nav></div><div class="pick-body${bodyClass ? ` ${bodyClass}` : ''}">${body}</div></div>`;
}
}
/** The rules for every pane of a pick block, in the order they appear: one rule per pane, so the CSS is written for the page. */
/** The rules for every pane of a pick block, in the order they appear: one rule per pane, so the CSS is written for the page. */
-function pickRules(key, ids) {
- return ids.map((id) => `.pick:has(#pk-${key}-${id}:checked) #pp-${key}-${id}{display:block}`).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.
@@ -47,4 +44,4 @@ function foldScript(id) {
@@ -47,4 +44,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, pickRules, foldBlock, foldScript };
+module.exports = { pickBlock, foldBlock, foldScript };
@@ -1,6 +1,5 @@
@@ -1,6 +1,5 @@
'use strict';
'use strict';
// The home on a phone (the owner's short-pages rule, 2026-10-30): the code samples are one pick block, a row of chips
// The home on a phone (the owner's short-pages rule, 2026-10-30): the code samples are one pick block, a row of chips
-// picks one of them, and the parts a reader rarely needs are folds. Radio inputs and :has(), no script for the chips;
// every block stays in the page for crawlers; a wider screen shows them all and no chips.
// every block stays in the page for crawlers; a wider screen shows them all and no chips.
const assert = require('assert');
const assert = require('assert');
const fs = require('fs');
const fs = require('fs');
@@ -37,17 +36,15 @@ const path = require('path');
@@ -37,17 +36,15 @@ const path = require('path');
// The code samples: one pick block, a chip per sample, the first one picked.
// The code samples: one pick block, a chip per sample, the first one picked.
assert.ok(text.includes('<div class="pick" id="pick-api">'), 'the code samples are one pick block');
assert.ok(text.includes('<div class="pick" id="pick-api">'), 'the code samples are one pick block');
- assert.match(text, /<div class="pick-row" role="radiogroup" aria-label="The ways to call Media">/);
- const chips = [...text.matchAll(/<input type="radio" name="pick-api" id="pk-api-([a-z0-9-]+)" value="\1"( checked)?><label for="pk-api-\1">([^<]+)<\/label>/g)];
+ assert.match(text, /<nav class="pick-row" aria-label="The ways to call Media">/);
+ const chips = [...text.matchAll(/<a class="pick-chip" href="#(?:pp-api-)?([a-z0-9-]+)">()([^<]+)<\/a>/g)];
assert.deepStrictEqual(chips.map((m) => m[1]), ['thumbnail', 'node', 's3'], 'a chip per sample');
assert.deepStrictEqual(chips.map((m) => m[1]), ['thumbnail', 'node', 's3'], 'a chip per sample');
- assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), ['thumbnail'], 'the first sample is picked');
+ assert.ok(!/type="radio"/.test(text), 'no chip is a radio: nothing is picked, every pane shows');
assert.strictEqual((text.match(/class="pick-pane"/g) || []).length, chips.length, 'one pane per chip');
assert.strictEqual((text.match(/class="pick-pane"/g) || []).length, chips.length, 'one pane per chip');
// Every sample stays in the page for the crawlers.
// Every sample stays in the page for the crawlers.
for (const label of ['Thumbnail', 'Node (openvibe-sdk)', 'S3 (any tool)']) {
for (const label of ['Thumbnail', 'Node (openvibe-sdk)', 'S3 (any tool)']) {
assert.ok(text.includes(`<figcaption>${label}</figcaption>`), `${label} is in the page`);
assert.ok(text.includes(`<figcaption>${label}</figcaption>`), `${label} is in the page`);
}
}
- // The rule that shows the picked sample is written for this page.
- assert.ok(text.includes('.pick:has(#pk-api-thumbnail:checked) #pp-api-thumbnail{display:block}'), 'the picked sample shows');
// The folds: open in the page, closed on a phone, and their content stays in the page.
// The folds: open in the page, closed on a phone, and their content stays in the page.
for (const id of ['what', 'limits', 'questions', 'shipped']) {
for (const id of ['what', 'limits', 'questions', 'shipped']) {
@@ -65,12 +62,10 @@ const path = require('path');
@@ -65,12 +62,10 @@ const path = require('path');
assert.ok(text.includes('class="sc-sec sc-media"'), 'the newest media rail is still there');
assert.ok(text.includes('class="sc-sec sc-media"'), 'the newest media rail is still there');
assert.ok(text.includes('id="api"'), 'the code samples are still there, under their own anchor');
assert.ok(text.includes('id="api"'), 'the code samples are still there, under their own anchor');
- // The CSS guard: the chips are hidden and the blocks all show until 640 px and where :has() is understood.
const css = fs.readFileSync(path.join(__dirname, '..', 'server', 'public', 'browse.js'), 'utf8');
const css = fs.readFileSync(path.join(__dirname, '..', 'server', 'public', 'browse.js'), '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-pane\{display:none\}/, 'the blocks are hidden on a phone');
+ assert.match(css, /\.pick-head,\.pick-row\{display:none\}/, 'no chips where there is room');
+ assert.doesNotMatch(css, /\.pick-pane\{display:none\}/, 'no pane is ever hidden (the owner, 2026-10-31)');
+ assert.match(css, /\.pick-chip\{/, 'the chips are links');
console.log('home short: all checks passed');
console.log('home short: all checks passed');
} finally {
} finally {