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(`${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 ? `The full read · ${esc(option.label)}
${flipChip('Front')}The full read · ${esc(option.label)}
` : ''} + ${backFacts(option)} +