openvibers/openvibe.space Commits
Home: every pane shows on a phone; the chips are links, not a picker (#173)
Changes
@@@ -83,29 +83,22 @@ body {/* Touch targets on phones (fleet audit 2026-10-29): the touch-only hit area for this group. Nothing moves with a mouse. */@media (pointer: coarse) { .space-faq summary { min-height: 44px; box-sizing: border-box; } }--/* Pick (the owner's short-pages rule, 2026-10-30): on a phone the front page's six parts are one at a time, picked-- with a row of chips. Radio inputs and :has(), no script: the chips and the one-part rule live under 640 px and only-- where :has() is understood, so a wider screen, or an older browser, shows every part and no chips. Every part stays-- in the page for crawlers. */--.pick-head { display: none; }--.pick-row { display: none; }--@supports selector(:has(*)) { @media (max-width: 640px) {++/* Pick: on a phone every pane is 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). A wide screen shows the panes as++ before, with no chips. */++.pick-head, .pick-row { display: none; }++@media (max-width: 640px) {.pick-bar { margin-top: 30px; }.pick-head { display: block; }.pick-head h2 { margin: 0 0 4px; font: 700 clamp(20px, 2.4vw, 28px)/1.2 var(--ov-font, 'Inter', system-ui, sans-serif); letter-spacing: -.02em; }.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(--border, #1f2d47); color: var(--text-secondary, #96a7c2); font-size: .9rem; font-weight: 650; white-space: nowrap; cursor: pointer; }-- .pick-row input:checked + label { color: var(--text-primary, #e6edf7); border-color: var(--accent, #3b82f6); background: color-mix(in srgb, var(--accent, #3b82f6) 12%, transparent); }-- .pick-row input:focus-visible + label { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }-- .pick .pick-pane { display: none; }-- /* A link that points at a part (the "How it works" button) opens that part on a phone too. */-- .pick .pick-pane:target { display: block !important; }-- .pick:has(.pick-pane:target) .pick-pane:not(:target) { display: none !important; }-- /* The chip names the part, so the part's own heading gives way to it. */-- .pick .pick-pane > .sc-sec > 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(--border, #1f2d47); color: var(--text-secondary, #96a7c2); font-size: .9rem; font-weight: 650; white-space: nowrap; text-decoration: none; }++ .pick-chip:hover, .pick-chip:focus-visible { color: var(--text-primary, #e6edf7); border-color: var(--accent, #3b82f6); }++ .pick-chip:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }++ .pick .pick-pane { scroll-margin-top: 72px; }++ .pick .pick-pane + .pick-pane { margin-top: 26px; }++}/* 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. */@@@ -10,7 +10,7 @@const config = require('../config');const sc = require('openvibe-shared/showcase');const { renderPage, escapeHtml: esc } = require('./layout');--const { pickBlock, pickRules, foldBlock } = require('./short');++const { pickBlock, foldBlock } = require('./short');const FORUM_URL = `${config.communityUrl}/s`;const FORUM_LABEL = FORUM_URL.replace(/^https?:\/\//, '');@@@ -152,7 +152,6 @@ curl -X POST "https://openvibe.space/api/v1/sites/$SITE_ID/deploys?activate=1" \canonicalPath: '/', active: 'home', styles: [sc.STYLESHEET], body, description: HOME_DESCRIPTION,agentLayer: true, llms: '/llms.txt',// The rule that shows the picked part of the home's pick block: one per part, written for this page.-- inlineStyles: [`@supports selector(:has(*)){@media (max-width:640px){${pickRules('parts', parts.map((p) => p.id))}}}`],jsonLd: [{ '@context': 'https://schema.org', '@type': 'WebSite', name: 'OpenVibe.Space', url: `${site}/`, description: HOME_DESCRIPTION },{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: HOME_FAQ.map((f) => ({ '@type': 'Question', name: f.q, acceptedAnswer: { '@type': 'Answer', text: f.a } })) },@@@ -22,22 +22,14 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</*/function pickBlock({ key, label, head = '', panes }) {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 the id the page already links to (an anchor in the hero, for instance), so a link to it still// opens that pane 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.@@@ -53,4 +45,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 };@@@ -18,10 +18,10 @@ const { boot, check, done } = require('./helpers/app');const page = r.text;const block = page.slice(page.indexOf('<div class="pick" id="pick-parts">'), page.indexOf('<div class="pick" id="pick-parts">') + 9000);assert.ok(block.startsWith('<div class="pick" id="pick-parts">'), 'the pick block is on the home');-- assert.match(block, /<div class="pick-row" role="radiogroup" aria-label="What Space does">/);-- const chips = [...block.matchAll(/<input type="radio" name="pick-parts" id="pk-parts-([a-z]+)" value="\1"( checked)?><label for="pk-parts-\1">([^<]+)<\/label>/g)];++ assert.match(block, /<nav class="pick-row" aria-label="What Space does">/);++ const chips = [...block.matchAll(/<a class="pick-chip" href="#(?:pp-parts-)?([a-z]+)">()([^<]+)<\/a>/g)];assert.ok(chips.length >= 5, `a chip per part (${chips.length})`);-- assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), [chips[0][1]], 'the first part is picked');++ assert.ok(!/type="radio"/.test(block), 'no chip is a radio: nothing is picked, every pane shows');// Every part 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 part is in the page, one per chip');@@@ -34,12 +34,8 @@ const { boot, check, done } = require('./helpers/app');await check('the CSS guard: the chips are hidden and the parts all show until 640 px and where :has() is understood', async () => {const r = await t.get('/');const page = r.text;-- assert.match(page, /<style>@supports selector\(:has\(\*\)\)\{@media \(max-width:640px\)\{\.pick:has\(#pk-parts-templates:checked\) #templates\{display:block\}/, 'the rule that shows the picked part');const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'space.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 parts are hidden on a phone');});await check('the parts a reader rarely needs are folds, open in the page and closed on a phone', async () => {
@@ -83,29 +83,22 @@ body {
@@ -83,29 +83,22 @@ body {
/* Touch targets on phones (fleet audit 2026-10-29): the touch-only hit area for this group. Nothing moves with a mouse. */
/* Touch targets on phones (fleet audit 2026-10-29): the touch-only hit area for this group. Nothing moves with a mouse. */
@media (pointer: coarse) { .space-faq summary { min-height: 44px; box-sizing: border-box; } }
@media (pointer: coarse) { .space-faq summary { min-height: 44px; box-sizing: border-box; } }
-/* Pick (the owner's short-pages rule, 2026-10-30): on a phone the front page's six parts are one at a time, picked
- with a row of chips. Radio inputs and :has(), no script: the chips and the one-part rule live under 640 px and only
- where :has() is understood, so a wider screen, or an older browser, shows every part and no chips. Every part stays
- in the page for crawlers. */
-.pick-head { display: none; }
-.pick-row { display: none; }
-@supports selector(:has(*)) { @media (max-width: 640px) {
+/* Pick: on a phone every pane is 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). A wide screen shows the panes as
+ before, with no chips. */
+.pick-head, .pick-row { display: none; }
+@media (max-width: 640px) {
.pick-bar { margin-top: 30px; }
.pick-bar { margin-top: 30px; }
.pick-head { display: block; }
.pick-head { display: block; }
.pick-head h2 { margin: 0 0 4px; font: 700 clamp(20px, 2.4vw, 28px)/1.2 var(--ov-font, 'Inter', system-ui, sans-serif); letter-spacing: -.02em; }
.pick-head h2 { margin: 0 0 4px; font: 700 clamp(20px, 2.4vw, 28px)/1.2 var(--ov-font, 'Inter', system-ui, sans-serif); letter-spacing: -.02em; }
.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(--border, #1f2d47); color: var(--text-secondary, #96a7c2); font-size: .9rem; font-weight: 650; white-space: nowrap; cursor: pointer; }
- .pick-row input:checked + label { color: var(--text-primary, #e6edf7); border-color: var(--accent, #3b82f6); background: color-mix(in srgb, var(--accent, #3b82f6) 12%, transparent); }
- .pick-row input:focus-visible + label { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }
- .pick .pick-pane { display: none; }
- /* A link that points at a part (the "How it works" button) opens that part on a phone too. */
- .pick .pick-pane:target { display: block !important; }
- .pick:has(.pick-pane:target) .pick-pane:not(:target) { display: none !important; }
- /* The chip names the part, so the part's own heading gives way to it. */
- .pick .pick-pane > .sc-sec > 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(--border, #1f2d47); color: var(--text-secondary, #96a7c2); font-size: .9rem; font-weight: 650; white-space: nowrap; text-decoration: none; }
+ .pick-chip:hover, .pick-chip:focus-visible { color: var(--text-primary, #e6edf7); border-color: var(--accent, #3b82f6); }
+ .pick-chip:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }
+ .pick .pick-pane { scroll-margin-top: 72px; }
+ .pick .pick-pane + .pick-pane { margin-top: 26px; }
+}
/* 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. */
@@ -10,7 +10,7 @@
@@ -10,7 +10,7 @@
const config = require('../config');
const config = require('../config');
const sc = require('openvibe-shared/showcase');
const sc = require('openvibe-shared/showcase');
const { renderPage, escapeHtml: esc } = require('./layout');
const { renderPage, escapeHtml: esc } = require('./layout');
-const { pickBlock, pickRules, foldBlock } = require('./short');
+const { pickBlock, foldBlock } = require('./short');
const FORUM_URL = `${config.communityUrl}/s`;
const FORUM_URL = `${config.communityUrl}/s`;
const FORUM_LABEL = FORUM_URL.replace(/^https?:\/\//, '');
const FORUM_LABEL = FORUM_URL.replace(/^https?:\/\//, '');
@@ -152,7 +152,6 @@ curl -X POST "https://openvibe.space/api/v1/sites/$SITE_ID/deploys?activate=1" \
@@ -152,7 +152,6 @@ curl -X POST "https://openvibe.space/api/v1/sites/$SITE_ID/deploys?activate=1" \
canonicalPath: '/', active: 'home', styles: [sc.STYLESHEET], body, description: HOME_DESCRIPTION,
canonicalPath: '/', active: 'home', styles: [sc.STYLESHEET], body, description: HOME_DESCRIPTION,
agentLayer: true, llms: '/llms.txt',
agentLayer: true, llms: '/llms.txt',
// The rule that shows the picked part of the home's pick block: one per part, written for this page.
// The rule that shows the picked part of the home's pick block: one per part, written for this page.
- inlineStyles: [`@supports selector(:has(*)){@media (max-width:640px){${pickRules('parts', parts.map((p) => p.id))}}}`],
jsonLd: [
jsonLd: [
{ '@context': 'https://schema.org', '@type': 'WebSite', name: 'OpenVibe.Space', url: `${site}/`, description: HOME_DESCRIPTION },
{ '@context': 'https://schema.org', '@type': 'WebSite', name: 'OpenVibe.Space', url: `${site}/`, description: HOME_DESCRIPTION },
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: HOME_FAQ.map((f) => ({ '@type': 'Question', name: f.q, acceptedAnswer: { '@type': 'Answer', text: f.a } })) },
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: HOME_FAQ.map((f) => ({ '@type': 'Question', name: f.q, acceptedAnswer: { '@type': 'Answer', text: f.a } })) },
@@ -22,22 +22,14 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</
@@ -22,22 +22,14 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</
*/
*/
function pickBlock({ key, label, head = '', panes }) {
function pickBlock({ key, label, head = '', panes }) {
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 the id the page already links to (an anchor in the hero, for instance), so a link to it still
// The pane carries the id the page already links to (an anchor in the hero, for instance), so a link to it still
// opens that pane on a phone (the CSS shows the pane a link points at).
// opens that pane 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('');
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.
@@ -53,4 +45,4 @@ function foldScript(id) {
@@ -53,4 +45,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 };
@@ -18,10 +18,10 @@ const { boot, check, done } = require('./helpers/app');
@@ -18,10 +18,10 @@ const { boot, check, done } = require('./helpers/app');
const page = r.text;
const page = r.text;
const block = page.slice(page.indexOf('<div class="pick" id="pick-parts">'), page.indexOf('<div class="pick" id="pick-parts">') + 9000);
const block = page.slice(page.indexOf('<div class="pick" id="pick-parts">'), page.indexOf('<div class="pick" id="pick-parts">') + 9000);
assert.ok(block.startsWith('<div class="pick" id="pick-parts">'), 'the pick block is on the home');
assert.ok(block.startsWith('<div class="pick" id="pick-parts">'), 'the pick block is on the home');
- assert.match(block, /<div class="pick-row" role="radiogroup" aria-label="What Space does">/);
- const chips = [...block.matchAll(/<input type="radio" name="pick-parts" id="pk-parts-([a-z]+)" value="\1"( checked)?><label for="pk-parts-\1">([^<]+)<\/label>/g)];
+ assert.match(block, /<nav class="pick-row" aria-label="What Space does">/);
+ const chips = [...block.matchAll(/<a class="pick-chip" href="#(?:pp-parts-)?([a-z]+)">()([^<]+)<\/a>/g)];
assert.ok(chips.length >= 5, `a chip per part (${chips.length})`);
assert.ok(chips.length >= 5, `a chip per part (${chips.length})`);
- assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), [chips[0][1]], 'the first part is picked');
+ assert.ok(!/type="radio"/.test(block), 'no chip is a radio: nothing is picked, every pane shows');
// Every part stays in the page, with the id its chip picks.
// Every part 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 part is in the page, one per chip');
assert.deepStrictEqual(panes, chips.map((m) => m[1]), 'every part is in the page, one per chip');
@@ -34,12 +34,8 @@ const { boot, check, done } = require('./helpers/app');
@@ -34,12 +34,8 @@ const { boot, check, done } = require('./helpers/app');
await check('the CSS guard: the chips are hidden and the parts all show until 640 px and where :has() is understood', async () => {
await check('the CSS guard: the chips are hidden and the parts all show until 640 px and where :has() is understood', async () => {
const r = await t.get('/');
const r = await t.get('/');
const page = r.text;
const page = r.text;
- assert.match(page, /<style>@supports selector\(:has\(\*\)\)\{@media \(max-width:640px\)\{\.pick:has\(#pk-parts-templates:checked\) #templates\{display:block\}/, 'the rule that shows the picked part');
const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'space.css'), 'utf8');
const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'space.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 parts are hidden on a phone');
});
});
await check('the parts a reader rarely needs are folds, open in the page and closed on a phone', async () => {
await check('the parts a reader rarely needs are folds, open in the page and closed on a phone', async () => {