From 17bc2701f323c0fc62bdc4c744e80e637c2229f6 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Mon, 27 Jul 2026 18:05:40 -0700 Subject: [PATCH] Put the full read on the card's back; the front is for choosing Field feedback: with every fact stacked under the media the cards ran past a screen tall. The front now carries only what the choice needs, sketch, lineage, title, thesis, identity, and the honest risk clamped to two lines, while first viewport and the case read on the back behind a Details chip, sharing the face with the board when the world has one. Risk stays on the front because the counterweights are pointless if the downside hides behind a flip, and once the sketch lands the first viewport is a picture anyway. The schema notes now ask for one-sentence facts, since a long fact should cost the reader a flip, not the page its scanability. Co-Authored-By: Claude Fable 5 --- skill/scripts/serve-question.mjs | 49 +++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 16 deletions(-) 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