diff --git a/.gitignore b/.gitignore index f187251a5..9ef8075c5 100644 --- a/.gitignore +++ b/.gitignore @@ -123,7 +123,8 @@ talks/ .astro/ # Local-only scratch for exploratory scripts, parked pages, and unused asset candidates. -tmp/ +tmp/* +!tmp/questionaire/ # Isolated ablation staging (impeccable-evals copies the built staged skill here # per rule, removes one rule, points a worker at it). Throwaway; never committed. diff --git a/tmp/questionaire/.gitignore b/tmp/questionaire/.gitignore new file mode 100644 index 000000000..8b9a162d8 --- /dev/null +++ b/tmp/questionaire/.gitignore @@ -0,0 +1,9 @@ +# Allowlist only the gallery toolchain and HTML outputs; keep captures and diag local. +* +!.gitignore +!build-gallery.mjs +!build-gallery-replica.mjs +!capture-previews.mjs +!gallery-cells.mjs +!previews-gallery.html +!previews-gallery-improved.html diff --git a/tmp/questionaire/build-gallery-replica.mjs b/tmp/questionaire/build-gallery-replica.mjs new file mode 100644 index 000000000..4af6f3526 --- /dev/null +++ b/tmp/questionaire/build-gallery-replica.mjs @@ -0,0 +1,149 @@ +/* Build an untouched replica of previews-gallery.html, then apply color-only + experiments to selected strategy iframes. The source gallery stays baseline. */ + +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const SOURCE = path.join(HERE, 'previews-gallery.html'); +const OUTPUT = path.join(HERE, 'previews-gallery-improved.html'); + +const OPS_PREVIEW = + '#picker-form .picker-strategy-stage > .picker-preview.picker-preview--ops'; + +/** @type {{ id: string, cell: string, css: string }[]} */ +const EXPERIMENTS = [ + { + id: 'landing-drenched-color-roles', + cell: 'strategy--persuade--drenched', + css: [ + 'html[data-cell-screen="03"][data-cell-surface="persuade"][data-cell-option="drenched"]', + '#picker-form .picker-strategy-stage > .picker-preview[data-surface="persuade"] {', + ' --pv-secondary: var(--pkc-secondary) !important;', + ' --pv-tertiary: var(--pkc-tertiary) !important;', + ' --pv-bone: color-mix(in oklab, var(--pkc-p-ink) 55%, var(--pkc-primary)) !important;', + ' --pv-strong: var(--pkc-p-ink) !important;', + ' --pv-secondary-wash: color-mix(in oklab, var(--pkc-p-ink) 16%, var(--pkc-primary)) !important;', + '}', + 'html[data-cell-screen="03"][data-cell-surface="persuade"][data-cell-option="drenched"]', + '#picker-form .picker-strategy-stage > .picker-preview[data-surface="persuade"] .pv-image {', + ' background: linear-gradient(', + ' 135deg,', + ' color-mix(in oklab, var(--pkc-p-ink) 16%, var(--pkc-primary)),', + ' color-mix(in oklab, var(--pkc-p-ink) 28%, var(--pkc-primary))', + ' ) !important;', + ' border-color: color-mix(in oklab, var(--pkc-p-ink) 40%, var(--pkc-primary)) !important;', + '}', + 'html[data-cell-screen="03"][data-cell-surface="persuade"][data-cell-option="drenched"]', + '#picker-form .picker-strategy-stage > .picker-preview[data-surface="persuade"] .pv-actions i:last-child {', + ' border-color: color-mix(in oklab, var(--pkc-p-ink) 45%, var(--pkc-primary)) !important;', + ' background: color-mix(in oklab, var(--pkc-p-ink) 12%, var(--pkc-primary)) !important;', + '}', + ].join('\n'), + }, + { + id: 'ops-committed-color-visibility', + cell: 'strategy--operate--committed', + css: [ + 'html[data-cell-screen="03"][data-cell-surface="operate"][data-cell-option="committed"]', + `${OPS_PREVIEW} {`, + ' /* Committed: primary owns the paper and the selection wash, not a 5% trace.', + ' Structure stays neutral like Restrained so accents still pop. */', + ' --pv-neutral: color-mix(in oklab, var(--pkc-neutral) 84%, var(--pkc-primary)) !important;', + ' --po-wash: color-mix(in oklab, var(--pkc-primary) 24%, var(--pkc-neutral)) !important;', + '}', + ].join('\n'), + }, + { + id: 'ops-full-palette-color-roles', + cell: 'strategy--operate--full-palette', + css: [ + 'html[data-cell-screen="03"][data-cell-surface="operate"][data-cell-option="full-palette"]', + `${OPS_PREVIEW} {`, + ' /* Full palette wireframe never assigns secondary; spread all four roles. */', + ' --po-wash: color-mix(in oklab, var(--pkc-secondary) 22%, var(--pkc-neutral)) !important;', + '}', + 'html[data-cell-screen="03"][data-cell-surface="operate"][data-cell-option="full-palette"]', + `${OPS_PREVIEW} .po-row--on .po-dot {`, + ' background: var(--pkc-tertiary) !important;', + '}', + 'html[data-cell-screen="03"][data-cell-surface="operate"][data-cell-option="full-palette"]', + `${OPS_PREVIEW} .po-field {`, + ' border-color: var(--pkc-secondary) !important;', + '}', + 'html[data-cell-screen="03"][data-cell-surface="operate"][data-cell-option="full-palette"]', + `${OPS_PREVIEW} .po-chart i:nth-child(2) {`, + ' background: color-mix(in oklab, var(--pkc-secondary) 78%, var(--pkc-neutral)) !important;', + '}', + 'html[data-cell-screen="03"][data-cell-surface="operate"][data-cell-option="full-palette"]', + `${OPS_PREVIEW} .po-chart i:nth-child(3) {`, + ' background: color-mix(in oklab, var(--pkc-tertiary) 68%, var(--pkc-neutral)) !important;', + '}', + ].join('\n'), + }, +]; + +const experiment = String.raw` +`; + +const html = await readFile(SOURCE, 'utf8'); +if (html.includes('data-gallery-replica-experiment')) { + throw new Error('Source gallery already contains the replica experiment'); +} +if (!html.includes('
', + 'Ground-truth extraction of every questionnaire preview except type scale and iconography: 75 cells, one per valid surface and option combination. The control bar repaints every cell through the picker\'s own custom-property machinery; hover a Motion cell to restart its scene from the first frame.
', + controls, + sectionsHtml, + ' + + + +')) { + throw new Error('Source gallery has no closing body tag'); +} + +for (const { cell } of EXPERIMENTS) { + if (!html.includes('data-cell="' + cell + '"')) { + throw new Error('Target cell not found: ' + cell); + } +} + +let replica = html.replace('', experiment + '\n'); +replica = replica.replace( + '
', + '
', +); +replica = replica.replace( + '
', + '
', +); +replica = replica.replace( + /
Ground-truth extraction[\s\S]*?<\/p>/, + '
Replica of the ground-truth gallery with color-only experiments on ' + + 'Landing page + Drenched, ' + + 'App UI + Committed, and ' + + 'App UI + Full palette. ' + + 'Baseline: previews-gallery.html. ' + + 'Restrained is unchanged (reference).
',
+);
+
+await writeFile(OUTPUT, replica);
+console.log('wrote previews-gallery-improved.html (' + (replica.length / 1024 / 1024).toFixed(1) + ' MB)');
diff --git a/tmp/questionaire/build-gallery.mjs b/tmp/questionaire/build-gallery.mjs
new file mode 100644
index 000000000..fb4f97816
--- /dev/null
+++ b/tmp/questionaire/build-gallery.mjs
@@ -0,0 +1,545 @@
+
+/* Plan 6 builder. Reads capture.json and writes previews-gallery.html: one
+ self-contained page with a sticky control bar (palette presets 1-4, per
+ role custom color inputs, font presets 1-4, jump navigation) over 86 cells,
+ each an iframe rebuilt from the captured ground truth. Zero runtime network
+ requests: all CSS resources are data URIs and every captured is
+ neutralized. */
+
+import { readFile, writeFile } from 'node:fs/promises';
+import { CELL_LIST, FONT_PRESETS, PALETTE_PRESETS, SECTIONS } from './gallery-cells.mjs';
+
+const OUT_DIR = '/Users/abdulwahab/impeccable/tmp/questionaire';
+const BLANK_GIF = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
+
+const neutralizeImages = (html) =>
+ html.replace(/(
]*?\bsrc=")[^"]*(")/g, '$1' + BLANK_GIF + '$2');
+
+/* One external form-state block per cell: every radio group the picker CSS
+ can read, except the group whose radios already live inside the captured
+ section markup, plus the committed palette fields. surface-modes checkboxes
+ are added except on screen 01b, whose section carries its own. */
+function externalInputs(cell) {
+ const parts = [];
+ if (cell.screen !== '01b') {
+ for (const s of ['persuade', 'operate', 'read', 'experience']) {
+ parts.push('');
+ }
+ }
+ parts.push('');
+ for (const role of ['primary', 'secondary', 'tertiary', 'neutral']) {
+ parts.push('');
+ }
+ const sectionGroup = SECTIONS.find((s) => s.id === cell.section).group;
+ for (const [group, value] of Object.entries(cell.formState)) {
+ if (group === sectionGroup) continue;
+ parts.push('');
+ }
+ return parts.join('');
+}
+
+/* The standalone document each cell iframe runs. Same placeholder scheme as
+ tmp/motion-previews-premium.html. */
+const IFRAME_TEMPLATE = [
+ '',
+ '',
+ '
', + '__CSS__', + '', + '', + '
+