diff --git a/skill/scripts/serve-question.mjs b/skill/scripts/serve-question.mjs index 555219aba..195d9f0ab 100644 --- a/skill/scripts/serve-question.mjs +++ b/skill/scripts/serve-question.mjs @@ -151,7 +151,7 @@ if (hasFlag('schema')) { canonCard: { label: 'The category standard', thesis: 'What this category ships, executed impeccably.', viewport: 'The arrangement a visitor expects, at full craft.', sketch: '.impeccable/sketches/canon.webp' }, steer: true, }, null, 2)); - console.log('\nOption ids return verbatim in ANSWER; "reroll" and "canon" are reserved. hero/board/sketch accept URLs or local paths; sketch slots may point at files that do not exist yet (serve first, generate after; the page polls until they land, so never block serving on generation). hero on a challenger is the inspiration it draws from and renders picture-in-picture beside the sketch, never as the promise of the build. canonCard renders the standing exit as a subordinate card with the same anatomy; without it, canon stays a quiet footer action. Include canon only for visual-direction rounds; never present it as your own recommendation.'); + console.log('\nOption ids return verbatim in ANSWER; "reroll" and "canon" are reserved. hero/board/sketch accept URLs or local paths; sketch slots may point at files that do not exist yet (serve first, generate after; the page polls until they land, so never block serving on generation). hero on a challenger is the inspiration it draws from and renders picture-in-picture beside the sketch, never as the promise of the build. canonCard renders the standing exit as a subordinate card with the same anatomy; without it, canon stays a quiet footer action. Include canon only for visual-direction rounds; never present it as your own recommendation. Keep thesis and each fact to one short sentence: the card front shows thesis, identity, and a two-line risk, while first viewport and the case read on the card back behind the Details chip, so long facts cost the reader a flip, not the page its scanability.'); process.exit(0); } @@ -310,6 +310,8 @@ function page() { // carries them; a plain body falls back to the prose block. Palette chips // and material tags give a text-only direction an immediate identity that // no generation luck can distort. + const fact = (label, value, cls = '') => value ? `

${label}${esc(value)}

` : ''; + const hasBack = (option) => Boolean(option.viewport || option.case || (option.boardSrc && option.heroSrc)); const anatomy = (option) => { const rows = []; if (option.thesis) rows.push(`

${esc(option.thesis)}

`); @@ -321,31 +323,38 @@ function page() { idBits.push(option.materials.slice(0, 4).map((m) => `${esc(m)}`).join('')); } if (idBits.length) rows.push(`
${idBits.join('')}
`); - const fact = (label, value) => value ? `

${label}${esc(value)}

` : ''; - rows.push(fact('First viewport', option.viewport)); - rows.push(fact('The case', option.case)); - rows.push(fact('Risk', option.risk)); + // The front carries only what the choice needs: thesis, identity, and the + // honest risk clamped to two lines. First viewport and the case read on + // the card's back; once the sketch lands, the first viewport is a picture. + rows.push(fact('Risk', option.risk, 'clamp')); if (!option.thesis && option.body) rows.push(`

${esc(option.body)}

`); - else if (option.body && option.thesis) rows.push(`

${esc(option.body)}

`); + else if (option.body && option.thesis && !hasBack(option)) rows.push(`

${esc(option.body)}

`); return rows.join('\n '); }; + const backFacts = (option) => [ + fact('First viewport', option.viewport), + fact('The case', option.case), + fact('Risk', option.risk), + option.body && option.thesis ? `

${esc(option.body)}

` : '', + ].filter(Boolean).join('\n '); const media = (option) => { const inspiration = option.heroSrc ? `
inspiration
` : ''; + const details = hasBack(option) ? flipChip('Details') : ''; if (option.sketchSrc) { return `
sketching…
${inspiration} -
${expandChip}${option.boardSrc ? flipChip('Board') : ''}
+
${expandChip}${details}
`; } if (option.heroSrc || option.boardSrc) { return `
-
${expandChip}${option.boardSrc && option.heroSrc ? flipChip('Board') : ''}
+
${expandChip}${details}
`; } return ''; @@ -363,14 +372,15 @@ function page() { - ${option.boardSrc && option.heroSrc ? `
-
+ ${hasBack(option) ? `
+ ${option.boardSrc ? `
-
${expandChip}${flipChip('Hero')}
-
-
-

Design-system board · ${esc(option.label)}

- +
${expandChip}${flipChip('Front')}
+
` : `

The full read · ${esc(option.label)}

${flipChip('Front')}
`} +
+ ${option.boardSrc ? `

The full read · ${esc(option.label)}

` : ''} + ${backFacts(option)} +
` : ''}
@@ -477,7 +487,7 @@ function page() { .face.text-only .body { padding-top: 12px; } .media { position: relative; width: 100%; aspect-ratio: 16/9; flex: none; } .media img { width: 100%; height: 100%; object-fit: cover; display: block; background: linear-gradient(100deg, var(--ks-graphite) 40%, var(--ks-graphite-2) 50%, var(--ks-graphite) 60%); } - .face.back { background: var(--ks-lacquer-deep); } + .face.back { background: var(--ks-lacquer-raised); } .back-bar { margin-top: auto; background: var(--ks-lacquer-raised); } .hero-blank { width: 100%; height: 100%; background: linear-gradient(100deg, var(--ks-graphite) 40%, var(--ks-graphite-2) 50%, var(--ks-graphite) 60%); } .back-bar { flex: none; flex-direction: row; align-items: center; justify-content: space-between; gap: .8rem; } @@ -497,6 +507,13 @@ function page() { .tag { font-family: var(--ks-mono); font-size: .6rem; letter-spacing: .14em; text-transform: uppercase; color: var(--ks-text-muted); border: 1px solid var(--ks-rule); border-radius: 4px; padding: 3px 7px; } .fact { font-size: .8rem; color: var(--ks-text-muted); line-height: 1.45; } .fact-label { display: inline-block; font-family: var(--ks-mono); font-size: .6rem; letter-spacing: .18em; text-transform: uppercase; color: var(--ks-text-faint); margin-right: .55em; transform: translateY(-1px); } + .fact.clamp { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } + /* The back is the full read: first viewport, the case, the whole risk, and + the board when the world has one. */ + .back-head { display: flex; align-items: center; justify-content: space-between; gap: .8rem; padding: 14px 14px 0; } + .media.back-media { aspect-ratio: 16/6; } + .media.back-media img { width: 100%; height: 100%; object-fit: cover; } + .body.back-body { overflow-y: auto; flex: 1; scrollbar-width: thin; } /* Inspiration rides picture-in-picture: the catalog world explains where the direction comes from without promising what the build will look like. */ /* Hovering the inspiration takes over the whole media region; the sketch is