@bongos/core 1.19.463 → 1.19.465

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/.bongos-core.json +82 -27
  2. package/.claude/skills/design/SKILL.md +5 -1
  3. package/docs/adr/0222-the-image-family-under-the-hero-only-rule.md +36 -0
  4. package/docs/adr/0223-the-detector-is-two-tiers-in-the-kit.md +33 -0
  5. package/docs/adr/README.md +2 -0
  6. package/docs/file-map.md +1 -1
  7. package/docs/module-api-changelog.md +4 -0
  8. package/docs/recipes/ui-look-before-you-ship.md +2 -0
  9. package/modules/ui-design/kit/README.md +12 -0
  10. package/modules/ui-design/kit/check-mock.js +18 -5
  11. package/modules/ui-design/kit/lib.js +22 -1
  12. package/modules/ui-design/kit/render.js +4 -2
  13. package/modules/ui-design/kit/tells.js +204 -0
  14. package/modules/ui-design/module.json +1 -1
  15. package/modules/ui-design/skills/README.md +6 -2
  16. package/modules/ui-design/skills/brandkit/SKILL.md +62 -0
  17. package/modules/ui-design/skills/image-to-code/SKILL.md +66 -0
  18. package/modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md +62 -0
  19. package/modules/ui-design/skills/imagegen-frontend-web/SKILL.md +83 -0
  20. package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
  21. package/modules/ui-design/styles/grove/DESIGN.md +1 -1
  22. package/modules/ui-design/styles/grove/assets/grove-pod-cut.webp +0 -0
  23. package/modules/ui-design/styles/grove/assets/grove-pod-cut.webp.json +12 -0
  24. package/modules/ui-design/styles/grove/assets/grove-pod.jpg +0 -0
  25. package/modules/ui-design/styles/grove/assets/grove-pod.jpg.json +12 -0
  26. package/modules/ui-design/styles/grove/mock.html +15 -1
  27. package/package-lock.json +2 -2
  28. package/package.json +1 -1
  29. package/src/module-api.js +1 -1
  30. package/tests/ui_design_kit.mjs +86 -0
  31. package/tests/ui_design_skills.mjs +52 -0
@@ -108,9 +108,11 @@ async function main() {
108
108
  const found = [...logs, ...lib.auditFloors(facts, { width: W, mode }), ...lib.expectProblems(facts, st.expect)];
109
109
  const key = `${mode}-${w}-${name}`;
110
110
  await page.screenshot({ path: path.join(out, key + '.png') });
111
- report.renders[key] = { facts, problems: found };
112
- process.stdout.write(`${mode} ${w} ${name}: ${found.length ? 'PROBLEMS' : 'clean'} bar ${facts.bar ? facts.bar.h : '-'} expanded [${facts.expanded.join('|')}] anims ${facts.running} focus ${facts.activeId || facts.activeText || '-'}\n`);
111
+ const tells = lib.craftTells(facts);
112
+ report.renders[key] = { facts, problems: found, tells };
113
+ process.stdout.write(`${mode} ${w} ${name}: ${found.length ? 'PROBLEMS' : 'clean'} bar ${facts.bar ? facts.bar.h : '-'} expanded [${facts.expanded.join('|')}] anims ${facts.running} focus ${facts.activeId || facts.activeText || '-'}${tells.length ? ` tells ${tells.length}` : ''}\n`);
113
114
  for (const f of found) { process.stdout.write(` ${f}\n`); problems.push(`${key}: ${f}`); }
115
+ for (const t of tells) process.stdout.write(` ~ ${t}\n`);
114
116
  await ctx.close();
115
117
  }
116
118
  // reduced motion: the kill switch, exercised with the file's own interaction
@@ -0,0 +1,204 @@
1
+ 'use strict';
2
+ // tells.js — the anti-pattern detector as RULES over a page's source (task 1003329, ADR 0223;
3
+ // the [detector] group of impeccable's rebuild spec, rebuilt in-house from its documentation
4
+ // only). Two tiers, kept apart on purpose:
5
+ // • FLOORS — mechanical and unambiguous on every instance: a bounce easing (nothing on the
6
+ // platform overshoots; the switch has no spring), a card nested in a card. check-mock
7
+ // reports them as FAIL.
8
+ // • TELLS — the craft floor's generated-design signatures and general-quality reads that a
9
+ // reviewer verifies in context: the identical icon-heading-text grid, hero metrics,
10
+ // decorative section numbering, a kicker above the h1, a coloured side stripe, a hard
11
+ // zero-blur offset shadow, a zero-offset coloured halo, decorative blur, a default typeface
12
+ // as the voice, a wide measure, a long headline, the same entrance on every section, a
13
+ // geometric mask on a plate, unicode glyphs as icons, a rainbow or mesh gradient, the
14
+ // browser surfaces nobody themed, proportional numerals in data, tiny type. check-mock
15
+ // reports them as WARN: a clean scan is a floor, never proof, and a false positive is
16
+ // named out loud rather than silenced.
17
+ // Pure: text in, [tier, rule, detail] out. No model, no key, no network, no browser — the
18
+ // rendered twins of these rules (computed values) live in lib.js's factsInPage as `tells`.
19
+ // Waivers: `kit-ignore: <key>, <key> (reason)` in any comment of the file; `--raw` bypasses.
20
+
21
+ const VOID = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
22
+ const CARD = /\bcard\b/i;
23
+ const KICKER = /\b(label|eyebrow|kicker|overline)\b/i;
24
+ const ICONISH = /^(svg|i|img|span|figure)$/;
25
+ const DEFAULT_FACES = /font-family\s*:\s*["']?(Inter|Roboto|Arial|Helvetica(?: Neue)?|Poppins|Montserrat|Open Sans|Lato|system-ui|-apple-system|Segoe UI)\b/gi;
26
+ const GLYPHS = /[←-↓▶◀★☆✓✔●◆■▲▼➜➔]/g;
27
+
28
+ const strip = (html) => String(html).replace(/<!--[\s\S]*?-->/g, '').replace(/<script[\s\S]*?<\/script>/gi, '').replace(/<style[\s\S]*?<\/style>/gi, '').replace(/<[^>]+>/g, ' ');
29
+ const stylesOf = (raw) => [...raw.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/gi)].map((m) => m[1]).join('\n').replace(/\/\*[\s\S]*?\*\//g, '');
30
+
31
+ // The elements of a page in document order, each with its tag, class list, parent, children and
32
+ // the text that follows its opening tag. A stack walk, not a DOM: enough to see nesting and
33
+ // sibling order, which is all the structural tells need.
34
+ function scanTags(html) {
35
+ const src = String(html).replace(/<!--[\s\S]*?-->/g, '').replace(/<(script|style)\b[\s\S]*?<\/\1>/gi, '');
36
+ const nodes = []; const stack = [];
37
+ const re = /<\/?([a-zA-Z][\w-]*)([^>]*)>/g; let m;
38
+ while ((m = re.exec(src))) {
39
+ const tag = m[1].toLowerCase();
40
+ if (m[0][1] === '/') { for (let i = stack.length - 1; i >= 0; i--) if (nodes[stack[i]].tag === tag) { stack.length = i; break; } continue; }
41
+ const cls = (m[2].match(/class="([^"]*)"/) || [, ''])[1];
42
+ const parent = stack.length ? stack[stack.length - 1] : -1;
43
+ const node = { tag, cls, depth: stack.length, parent, children: [], text: src.slice(re.lastIndex, re.lastIndex + 240).split('<')[0].trim() };
44
+ const idx = nodes.push(node) - 1;
45
+ if (parent >= 0) nodes[parent].children.push(idx);
46
+ if (!VOID.has(tag) && !/\/\s*$/.test(m[2])) stack.push(idx);
47
+ }
48
+ return nodes;
49
+ }
50
+ const describe = (n) => n.tag + (n.cls ? '.' + n.cls.split(/\s+/)[0] : '');
51
+ function subtreeText(nodes, idx) {
52
+ const parts = [nodes[idx].text];
53
+ for (let i = idx + 1; i < nodes.length && nodes[i].depth > nodes[idx].depth; i++) parts.push(nodes[i].text);
54
+ return parts.join(' ');
55
+ }
56
+ // box-shadow layers as [x, y, blur, rest] strings; a value that is not a shadow list yields none.
57
+ function shadowLayers(css) {
58
+ const out = [];
59
+ for (const m of css.matchAll(/(?<![-\w])box-shadow\s*:\s*([^;}]+)/g)) {
60
+ for (const layer of m[1].split(/,(?![^(]*\))/)) {
61
+ const t = layer.trim().replace(/^inset\s+/, 'INSET ').split(/\s+/);
62
+ if (t[0] === 'INSET' || t[0] === 'none' || /^var\(/.test(t[0])) continue;
63
+ const px = (s) => (s === '0' ? 0 : /^-?\d*\.?\d+px$/.test(s) ? parseFloat(s) : NaN);
64
+ if (t.length >= 3 && !Number.isNaN(px(t[0])) && !Number.isNaN(px(t[1]))) out.push({ x: px(t[0]), y: px(t[1]), blur: px(t[2]), rest: t.slice(3).join(' '), src: layer.trim() });
65
+ }
66
+ }
67
+ return out;
68
+ }
69
+ const list = (arr, n = 4) => arr.slice(0, n).join(' | ') + (arr.length > n ? ` (+${arr.length - n})` : '');
70
+
71
+ // ── the floors (FAIL) ──────────────────────────────────────────────────────────
72
+ const FLOORS = {
73
+ 'bounce easing (a cubic-bezier that overshoots; the switch has no spring)': ({ css }) => {
74
+ const bad = [...css.matchAll(/cubic-bezier\(\s*[-\d.]+\s*,\s*([-\d.]+)\s*,\s*[-\d.]+\s*,\s*([-\d.]+)\s*\)/g)].filter((m) => +m[1] < 0 || +m[1] > 1 || +m[2] < 0 || +m[2] > 1).map((m) => m[0]);
75
+ return bad.length ? list([...new Set(bad)]) : null;
76
+ },
77
+ 'a card nested in a card': ({ nodes }) => {
78
+ const hits = [];
79
+ nodes.forEach((n) => { if (!CARD.test(n.cls)) return; for (let p = n.parent; p >= 0; p = nodes[p].parent) if (CARD.test(nodes[p].cls)) { hits.push(`${describe(n)} inside ${describe(nodes[p])}`); break; } });
80
+ return hits.length ? list(hits) : null;
81
+ },
82
+ };
83
+
84
+ // ── the tells (WARN; a reviewer verifies each in context) ─────────────────────
85
+ const TELLS = {
86
+ 'identical icon-heading-text card grid as page structure': ({ nodes }) => {
87
+ const sig = (i) => { const n = nodes[i]; const kids = n.children.map((c) => nodes[c].tag); return kids.length >= 2 && ICONISH.test(kids[0]) && kids.some((t) => /^h[2-6]$/.test(t)) && kids.includes('p') ? kids.join(',') : null; };
88
+ const hits = [];
89
+ for (const n of nodes) { const sigs = n.children.map(sig); let run = 1; for (let i = 1; i < sigs.length; i++) { run = sigs[i] && sigs[i] === sigs[i - 1] ? run + 1 : 1; if (run === 3) { hits.push(`${describe(n)}: ${sigs[i]} × 3+`); break; } } }
90
+ return hits.length ? list(hits) : null;
91
+ },
92
+ 'the hero-metric template (three or more statistics in the first section)': ({ nodes }) => {
93
+ const hero = nodes.findIndex((n) => n.tag === 'section' || /\bhero\b/i.test(n.cls));
94
+ if (hero < 0) return null;
95
+ const stats = subtreeText(nodes, hero).match(/(?<![\w.])\d{1,3}(?:[.,]\d+)?\s*(%|\+|k|K|M|x)(?![\w])/g) || [];
96
+ return stats.length >= 3 ? list(stats) : null;
97
+ },
98
+ 'decorative section numbering (01 / 02 / 03 as labels)': ({ text }) => {
99
+ const n = (text.match(/(?:^|\s)0[1-9](?=\s|$|\s*\/)/g) || []).length;
100
+ return n >= 2 ? `${n} numbered labels` : null;
101
+ },
102
+ 'a kicker or eyebrow label directly above the h1': ({ nodes }) => {
103
+ const h1 = nodes.findIndex((n) => n.tag === 'h1');
104
+ if (h1 < 0 || nodes[h1].parent < 0) return null;
105
+ const sibs = nodes[nodes[h1].parent].children; const at = sibs.indexOf(h1);
106
+ if (at < 1) return null;
107
+ const prev = nodes[sibs[at - 1]];
108
+ return /^(p|span|div|small)$/.test(prev.tag) && KICKER.test(prev.cls) ? `${describe(prev)} above h1` : null;
109
+ },
110
+ 'a coloured side stripe (border-left wider than a hairline)': ({ css }) => {
111
+ const hits = [...css.matchAll(/border-left\s*:\s*((?:[2-9]|\d{2,})px)\s+solid/g)].map((m) => m[1]);
112
+ return hits.length ? `${hits.length} × ${[...new Set(hits)].join(', ')}` : null;
113
+ },
114
+ 'a hard zero-blur offset shadow (a world that did not choose that language)': ({ css }) => {
115
+ const hits = shadowLayers(css).filter((l) => (l.x !== 0 || l.y !== 0) && l.blur === 0).map((l) => l.src);
116
+ return hits.length ? list(hits) : null;
117
+ },
118
+ 'a zero-offset coloured halo as elevation': ({ css }) => {
119
+ const hits = shadowLayers(css).filter((l) => l.x === 0 && l.y === 0 && l.blur > 0 && /^(var\(|color-mix\()/.test(l.rest.replace(/^-?\d+px\s+/, ''))).map((l) => l.src);
120
+ return hits.length ? list(hits) : null;
121
+ },
122
+ 'decorative glass and blur (backdrop-filter on more than two surfaces)': ({ css }) => {
123
+ const n = (css.match(/backdrop-filter\s*:/g) || []).length;
124
+ return n > 2 ? `${n} declarations` : null;
125
+ },
126
+ 'a default typeface as the voice (the pack\'s stacks are var(--font-display) / var(--font-body))': ({ css }) => {
127
+ const hits = [...css.matchAll(DEFAULT_FACES)].map((m) => m[1]);
128
+ return hits.length ? [...new Set(hits)].join(', ') : null;
129
+ },
130
+ 'a prose measure wider than 80 characters': ({ css }) => {
131
+ const hits = [...css.matchAll(/max-width\s*:\s*(\d+)ch/g)].map((m) => +m[1]).filter((n) => n > 80);
132
+ return hits.length ? hits.map((n) => n + 'ch').join(', ') : null;
133
+ },
134
+ 'a headline longer than two lines can hold (over 90 characters)': ({ raw }) => {
135
+ const h = (raw.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i) || [, ''])[1].replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
136
+ return h.length > 90 ? `${h.length} characters` : null;
137
+ },
138
+ 'the same entrance animation on three or more rules': ({ css }) => {
139
+ const count = {};
140
+ for (const m of css.matchAll(/(?<![-\w])animation(?:-name)?\s*:\s*([a-zA-Z_][\w-]*)/g)) if (!/^(none|inherit|initial|unset)$/.test(m[1])) count[m[1]] = (count[m[1]] || 0) + 1;
141
+ const hits = Object.entries(count).filter(([, n]) => n >= 3).map(([k, n]) => `${k} × ${n}`);
142
+ return hits.length ? hits.join(', ') : null;
143
+ },
144
+ 'a geometric mask faking an organic cutout on a plate': ({ css }) => {
145
+ const hits = [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].filter((m) => /\b(img|picture|plate)\b/i.test(m[1]) && /clip-path\s*:\s*(circle|ellipse|polygon)\(/.test(m[2])).map((m) => m[1].trim());
146
+ return hits.length ? list(hits) : null;
147
+ },
148
+ 'unicode glyphs standing in for a drawn icon set': ({ text }) => {
149
+ const n = (text.match(GLYPHS) || []).length;
150
+ return n >= 3 ? `${n} glyphs in visible copy` : null;
151
+ },
152
+ 'a rainbow or mesh gradient (conic, or four or more stops)': ({ css }) => {
153
+ const hits = [];
154
+ if (/conic-gradient\(/.test(css)) hits.push('conic-gradient');
155
+ // coloured stops only: a black/white/transparent scrim or mask with many stops is not a rainbow
156
+ for (const m of css.matchAll(/(?:linear|radial)-gradient\(((?:[^()]|\([^()]*\))*)\)/g)) { const stops = m[1].split(/,(?![^(]*\))/).filter((s) => /var\(|color-mix\(|hsl|#(?!(?:000|fff)\b)/i.test(s)); if (stops.length >= 4) hits.push(`${stops.length} coloured stops`); }
157
+ return hits.length ? list(hits) : null;
158
+ },
159
+ 'the browser surfaces nobody themed (selection, caret, underline offset)': ({ raw, css }) => {
160
+ const missing = [];
161
+ if (!/::selection/.test(css)) missing.push('::selection');
162
+ if (/<(input|textarea)\b/i.test(raw) && !/caret-color/.test(css)) missing.push('caret-color');
163
+ if (/<a\b/i.test(raw) && /text-decoration\s*:\s*underline/.test(css) && !/text-underline-offset/.test(css)) missing.push('text-underline-offset');
164
+ return missing.length ? missing.join(', ') : null;
165
+ },
166
+ 'proportional numerals in data (no tabular-nums beside four-digit figures)': ({ text, css }) => {
167
+ const figures = (text.match(/(?<![\w.])\d{1,3}(?:,\d{3})+(?![\w.])|(?<![\w.])\d{4,}(?![\w.])/g) || []).length;
168
+ return figures >= 3 && !/tabular-nums/.test(css) ? `${figures} figures, no tabular-nums` : null;
169
+ },
170
+ 'tiny type (a font-size under 11px)': ({ css }) => {
171
+ const hits = [...css.matchAll(/font-size\s*:\s*(\d+(?:\.\d+)?)px/g)].map((m) => +m[1]).filter((n) => n < 11);
172
+ return hits.length ? [...new Set(hits)].map((n) => n + 'px').join(', ') : null;
173
+ },
174
+ };
175
+
176
+ // `kit-ignore: key words, other key (reason)` in any comment: a per-file waiver with its reason.
177
+ // A key is matched by prefix against the rule's first words, so a waiver reads like the rule.
178
+ function parseWaivers(html) {
179
+ const out = new Map();
180
+ const comments = [...String(html).matchAll(/<!--([\s\S]*?)-->|\/\*([\s\S]*?)\*\//g)].map((m) => m[1] || m[2] || '');
181
+ for (const c of comments) for (const m of c.matchAll(/kit-ignore:\s*([^()\n]+?)\s*(?:\(([^)]*)\))?\s*(?:\n|$)/g)) for (const key of m[1].split(',').map((s) => s.trim().toLowerCase()).filter(Boolean)) out.set(key, (m[2] || '').trim());
182
+ return out;
183
+ }
184
+ const waiverFor = (waivers, rule) => { for (const [k, why] of waivers) if (rule.toLowerCase().startsWith(k)) return why || 'waived'; return null; };
185
+
186
+ // The scan: every floor and every tell over one page's source, as check-mock's result rows.
187
+ // `bans` is the instance's taste set: an instance that bans the eyebrow makes that tell a FAIL.
188
+ // `waivers` (a Map from parseWaivers) turns a hit into a WAIVE row; pass none for --raw.
189
+ function scanTells(html, { bans = new Set(), waivers = new Map() } = {}) {
190
+ const raw = String(html).replace(/\r\n/g, '\n');
191
+ const ctx = { raw, css: stylesOf(raw), text: strip(raw), nodes: scanTags(raw) };
192
+ const rows = [];
193
+ const add = (tier, rule, fn) => {
194
+ const detail = fn(ctx);
195
+ if (detail == null) return rows.push(['PASS', rule, '']);
196
+ const why = waiverFor(waivers, rule);
197
+ rows.push([why ? 'WAIVE' : tier, rule, why ? `${detail}; ${why}` : detail]);
198
+ };
199
+ for (const [rule, fn] of Object.entries(FLOORS)) add('FAIL', `floor: ${rule}`, fn);
200
+ for (const [rule, fn] of Object.entries(TELLS)) add(rule.startsWith('a kicker') && bans.has('eyebrow') ? 'FAIL' : 'WARN', `tell: ${rule}`, fn);
201
+ return rows;
202
+ }
203
+
204
+ module.exports = { scanTells, scanTags, parseWaivers, shadowLayers, FLOOR_RULES: Object.keys(FLOORS), TELL_RULES: Object.keys(TELLS) };
@@ -5,7 +5,7 @@
5
5
  "coreVersion": "^1.19.0",
6
6
  "default": true,
7
7
  "maintenance": { "status": "core-maintained" },
8
- "contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable"] },
8
+ "contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable", "image-to-code", "imagegen-frontend-web", "imagegen-frontend-mobile", "brandkit"] },
9
9
  "provides": [],
10
10
  "consumes": []
11
11
  }
@@ -21,7 +21,7 @@ Two origins were scanned for the set scoped with the owner (impeccable; the elev
21
21
  | Skill (in-house name) | Upstream | Licence | Verdict | Landing |
22
22
  |---|---|---|---|---|
23
23
  | `impeccable` | `pbakaus/impeccable` @ `<redacted>` (skill v4.1.x) | Apache-2.0 | **dangerous** — floor: 3 critical (a `bypassPermissions` marker in the skill's own live copy-edit agent; an instruction-override phrase in a test fixture; a same-file credential-store + http-client co-occurrence in a test), 19 high (shipped PostToolUse + Stop hooks, credential-store references), 107 medium, 147 low; panel: hooks written into five harness configs (globally on a global install), a child agent spawned with permissions bypassed from a loopback HTTP route, an autonomy directive that tells the agent to disregard its system prompt, a live-server token embedded in page source, an unverified remote bundle download | rebuild spec → [task 1003326](https://cloudbongos.com/builders#/task/1003326) → rebuilt in-house as `impeccable/` ([ADR 0221](../../../docs/adr/<redacted>.md)) |
24
- | `design-taste-frontend`, `design-taste-frontend-v1`, `high-end-visual-design`, `minimalist-ui`, `industrial-brutalist-ui`, `redesign-existing-projects`, `gpt-taste`, `stitch-design-taste`, `image-to-code`, `imagegen-frontend-web`, `imagegen-frontend-mobile`, `brandkit` | `Leonxlnx/taste-skill` @ `<redacted>` | MIT | **dangerous** — floor: 1 critical (an injection-phrase hit at `skills/taste-skill/SKILL.md:892`, which the panel identifies as a filename placeholder; the floor is untouchable by design); panel: caution (1 medium: the only documented install path runs an unpinned third-party `npx` CLI; 21 low: mandated invocation of any available image-generation tool, unbounded image counts, remote assets and scripts in generated pages, an auto-loaded cross-agent instruction file) | rebuild spec → GDS task |
24
+ | `design-taste-frontend`, `design-taste-frontend-v1`, `high-end-visual-design`, `minimalist-ui`, `industrial-brutalist-ui`, `redesign-existing-projects`, `gpt-taste`, `stitch-design-taste`, `image-to-code`, `imagegen-frontend-web`, `imagegen-frontend-mobile`, `brandkit` | `Leonxlnx/taste-skill` @ `<redacted>` | MIT | **dangerous** — floor: 1 critical (an injection-phrase hit at `skills/taste-skill/SKILL.md:892`, which the panel identifies as a filename placeholder; the floor is untouchable by design); panel: caution (1 medium: the only documented install path runs an unpinned third-party `npx` CLI; 21 low: mandated invocation of any available image-generation tool, unbounded image counts, remote assets and scripts in generated pages, an auto-loaded cross-agent instruction file) | rebuild spec → [task 1003325](https://cloudbongos.com/builders#/task/1003325) (the taste core, ADR 0220) and [task 1003328](https://cloudbongos.com/builders#/task/1003328) (the image family, ADR 0222) → rebuilt in-house |
25
25
 
26
26
  The eleven taste skills are single-file prose; the floor's one critical is a false positive in the human sense and a true positive in the scanner's — and the rule that makes the scanner worth having is that a session does not get to tell the difference. Vendoring any of them anyway is an **owner override recorded in an ADR**, not a judgement a task makes.
27
27
 
@@ -36,8 +36,12 @@ A rebuild spec becomes a **first-party skill** at `<name>/` — no `vendor/` seg
36
36
  | `high-end-visual-design` | `Leonxlnx/taste-skill` `soft-skill` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the agency-build polish pass: one texture archetype (the style library's looks) and one layout archetype per project, the nested container anatomy where the world allows a container, the pill with its nested arrow disc, rhythm, choreography and scroll entry inside the Kill Switch |
37
37
  | `redesign-existing-projects` | `Leonxlnx/taste-skill` `redesign-skill` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the in-place upgrade: scan the stack, diagnose against the seven-group audit with the instance's world as the answer key, fix in impact-per-risk order, one group per commit; keeps the stack, never migrates frameworks |
38
38
  | `impeccable` | `pbakaus/impeccable` @ `<redacted>` (skill v4.1.x) | [task 1003326](https://cloudbongos.com/builders#/task/1003326) | the design-craft method as one skill with sub-commands, one playbook loaded per request from `impeccable/reference/`: shape, critique, audit, polish, harden, onboard, animate, colorize, typeset, layout, adapt, distill, clarify, optimize, document, extract, bolder, quieter; the craft floor and the bounded finish live in its `SKILL.md`; the three persisted homes are the instance's own files; no hooks, no bypass agent, no telemetry, no live server (the playwright plugin and the kit are the loops) |
39
+ | `image-to-code` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the analyse-then-implement loop with the owner's references and the world as the source of truth; the only image it may cause is the hero plate; a structured per-image analysis, the extraction spec, a faithful translation, drift named as the failure mode, a clarity checklist the kit measures |
40
+ | `imagegen-frontend-web` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the plate-making skill: text-free hero plates, hero objects, backdrops and orbs for the slots a composition already has, a composition anchor per plate, one family per surface, the pack as the palette lock, the instance's art pipeline key, the pair-and-sidecar output contract; never a section image, a comp or a screen |
41
+ | `imagegen-frontend-mobile` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | portrait plates for the 390 and 320 compositions, clear of the regions a phone reserves, the first view held to one focal point, a scrim under text over a plate; a screen is the states file rendered by the kit; no phone mockups, no flows of screens; native declines |
42
+ | `brandkit` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the identity system with the strategy inferred first and the logo standard kept: the mark drawn as SVG, the colour system as the pack, the type specimen as the faces, the applications as the kit's renders, the image direction as a hero-object family, the board as a contact sheet; never a generated logo, board or mockup |
39
43
 
40
- What every rebuild does with the upstream's opinions, in the same words: the palette is the pack and a variant is a look from [`../styles/`](../styles/README.md); the design system is the instance's `DESIGN.md`; images are hero plates and hero objects by token and never a step; every motion rule lives inside the Kill Switch (springs, magnetic hover, pointer-tracked borders are named *not available*); the upstream's bans are defaults an instance's `DESIGN.md` overrides; the kit's renders are the pre-flight. Still specs, not skills: the image family (task 1003328), the detector (1003329), the four looks (1003327).
44
+ What every rebuild does with the upstream's opinions, in the same words: the palette is the pack and a variant is a look from [`../styles/`](../styles/README.md); the design system is the instance's `DESIGN.md`; images are hero plates and hero objects by token and never a step; every motion rule lives inside the Kill Switch (springs, magnetic hover, pointer-tracked borders are named *not available*); the upstream's bans are defaults an instance's `DESIGN.md` overrides; the kit's renders are the pre-flight. The image family (task 1003328, [ADR 0222](../../../docs/adr/<redacted>.md)) carries the one rewrite the upstream never had: images are for text-free hero plates and hero objects only, so a screen is the surface itself, the mark is drawn, the board is a contact sheet, and what ships is the pair (a black-ground JPEG for dark, an alpha WebP cutout for light) with a provenance sidecar beside every file, through the instance's art pipeline key. Still specs, not skills: the detector (task 1003329), the four looks (1003327).
41
45
 
42
46
  **The playwright plugin** (`claude-plugins-official`, an MCP plugin) is the LIVE browser-driving loop, where the kit is the headless one. It is a prerequisite, documented — `claude plugin install playwright@claude-plugins-official` — never vendored.
43
47
 
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: brandkit
3
+ description: The identity-system method for ANY Cloud Bongos instance, under the owner's hero-only rule: infers the brand strategy before anything is made (category, audience, function, promise, cultural position, the one symbolic metaphor, what to avoid), holds the logo standard and the five concept methods, and lands the system as the instance's own materials: the mark drawn as SVG in code, the colour system as the branding pack, the type specimen as the pack's faces, the applications as the platform's real surfaces rendered by the kit, and the image direction as a text-free hero-object family with a sidecar beside every plate. The nine-panel board becomes a contact sheet composed from those parts, never a generated image with text in it. Triggers on "brand kit", "identity system", "logo system", "brand guidelines board", "the visual world for a new instance", or when cloudbongos init asks what an instance's mark and material are. Never generates a logo, a wordmark, a board with text, or a mockup with UI. Ported in-house from the taste-skill rebuild spec (tasks 1003325 and 1003328, ADR 0198) with the hero-only rewrite the upstream never had.
4
+ ---
5
+
6
+ <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
7
+ > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
8
+ >
9
+ > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
10
+ > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
11
+ > 3. **A style skill produces variants INSIDE that world, never a second world.** The look this skill carries (its palette, faces, materials, mood boards) is a reference for composition and craft; the instance's tokens and faces replace it. Changing the world is the owner's decision and an ADR, not a session's taste.
12
+ > 4. **Images are for hero plates and hero objects only.** UI is built from templates and the instance's world, not painted; any image-generation step below applies to text-free plates and hero objects, never to controls, cards, text, or a screenshot of a screen.
13
+ > 5. **The instance's taste bans apply and win.** Read them from its `DESIGN.md`. Cloud Bongos's own (no em dash or en dash in visible copy, no emoji, no gradient text, no three-equal-card row, no fake screenshot built from divs) are that instance's, not the module's; where this skill's rules conflict with an instance's bans, the instance wins.
14
+ > 6. **The platform floors hold on every instance** and a `DESIGN.md` may tighten them, never loosen them: every control clears 24px on both sides (primary pills 46–48px); AA in both modes (4.5:1 body text, 3:1 large text and non-text) measured from the real stylesheet; and the Kill Switch, `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, no motion authored in JavaScript, and a rest frame that is a complete composition on its own.
15
+ > 7. **Look before you ship.** Render the real surface through the module's kit before and after (`/design` Step 2 and `docs/recipes/ui-look-before-you-ship.md`: every state at 1440 / 390 / 320 in dark and light, audited against the floors); a page with no states file gets one first, and new interactive code ships with a pin in the page's own test file.
16
+ >
17
+ > The rules below are the skill's own. A vendored copy carries a `PROVENANCE.md` beside this file naming the origin, the pinned commit, the licence and the scan verdict it was vendored under; an in-house rebuild names its rebuild spec and its ADR in the section right after this block.
18
+ <!-- END PLATFORM PREAMBLE -->
19
+
20
+ ## Rebuilt from
21
+
22
+ This skill is an **in-house rebuild**, not a vendored copy. Its documented functionality comes from `Leonxlnx/taste-skill` @ `<redacted>` (MIT; the `brandkit` entry), which `/scan-before-install` reduced to `dangerous` at the default tier on 2026-08-28, so under [ADR 0198](../../../../docs/adr/<redacted>.md) it could land only as a REBUILD-SPEC. The spec is the `[brandkit]` group in the body of [task 1003325](https://cloudbongos.com/builders#/task/1003325); the port is [task 1003328](https://cloudbongos.com/builders#/task/1003328); this file implements the requirements from scratch, **no upstream bytes consulted or copied**. The landing shape is [ADR 0220](../../../../docs/adr/<redacted>.md); the hero-only rewrite this family carries is [ADR 0222](../../../../docs/adr/<redacted>.md).
23
+
24
+ What changed in translation, on purpose: the upstream delivered **brand identity imagery**: logo systems, nine-panel guideline boards, identity decks and visual-world presentations, all as generated images carrying a brand name, a tagline, labels and interface chips. Under the owner's rule an image with letters in it is not a hero plate, and a mockup of an interface is a screenshot of a screen. So here every panel of the board lands as the thing it depicted: the **mark is drawn** (an inline SVG, the world's one mark, a code asset with a construction comment), the **colour system is the pack** (`config/branding.json`, the fifteen), the **type specimen is the pack's two faces** on a real page, the **applications are the platform's own surfaces** (the front door, the hall, a look's specimen) rendered by the kit, and the **image direction is a hero-object family**: one object in the world's materials, text-free, in pairs with sidecars. The board itself is a **contact sheet** assembled from the kit's shots and the plates, a review artifact rather than a generation. The strategy inference, the logo standard, the five concept methods, the rhythm, the colour discipline and the reference rule survive unchanged.
25
+
26
+ ## Scope: when this skill fires, and when it declines
27
+
28
+ **Fires** when an instance needs an identity system or its existing one needs to be written down: a new instance at `cloudbongos init`, a rebrand the owner has decided (an ADR, never a session's taste), a look for the style library that needs its metaphor and mark settled.
29
+
30
+ **Declines, and says so:** a generated logo or wordmark, a board or deck as a single image, a mockup with UI drawn into it, a reproduction of a supplied logo or slogan. The nearest thing it does instead is the system below, in code and plates.
31
+
32
+ ## Step 1: infer the strategy before making anything
33
+
34
+ Write it down, one line each, from `docs/project-context.template.md` and a round of at most three questions where the file is silent: **category**; **audience**; **what the product does**; the **emotional promise**; the **cultural position** (what it stands beside, what it stands against); the **one symbolic metaphor** (the chrome world's is a sculpture in a void; a garden of sculpted paper is the grove's); and **what the brand should avoid**. Symbols are chosen for meaning, never at random, and every later decision cites this block. An instance's answers live in its `DESIGN.md` Overview; this skill proposes the paragraph and the owner keeps it.
35
+
36
+ ## Step 2: the mark, drawn
37
+
38
+ The logo standard: simple, memorable, symbolic, scalable and balanced; usable as an icon, a wordmark companion, a badge, an interface mark and a pattern; refused by name: generic bolts, random animals, fake crests, clipart, and inconsistent variants. Five concept methods, at most two combined in one mark: the **meaningful monogram**, the **product action**, the **metaphor fusion**, **negative space**, and **construction geometry**. Propose two or three directions as sketches in words, get the owner's pick, then **draw the mark as an inline SVG** on the world's construction grid, with its geometry stated in a comment (the chrome world's bongo-pair glyph, drawn on the mark ramp, is the worked example). The mark's ramp is derived from the pack; a mark is never a raster and never a generated logo.
39
+
40
+ ## Step 3: the colour system is the pack
41
+
42
+ One base, one primary accent, one secondary accent and a neutral scale: that is the branding contract's thirteen colours, written into `config/branding.json` (or proposed as a look under `modules/ui-design/styles/` when the instance wants a library entry). Accents repeat across every surface; no rainbow spread, no generic glow palette. Every pairing is measured with the kit's `contrast.js` in both modes before it is called a system.
43
+
44
+ ## Step 4: the type specimen is the two faces
45
+
46
+ The pack's display and body stacks, shown on a real page as the hierarchy the world names (display, headline, title, body, label, caption, data), not on a generated board. Text discipline for anything the identity carries in public: the brand name, one short specific tagline, one address or command, a few section labels, short interface chips; no paragraphs of filler, no tiny fake body copy, no dense menus; the voice is the copy desk's.
47
+
48
+ ## Step 5: the applications are the surfaces
49
+
50
+ The physical and digital applications of an identity are rendered, not painted: the front door, the hall's chrome, a look's specimen through `node modules/ui-design/kit/render.js --page <path>` (`--style <name>` for a library look). A browser frame is the real browser; a terminal frame is the real CLI's output; a badge, a label or a card is the world's component. A data-heavy fake dashboard is refused in the same breath as a fake screenshot. Any motion an application carries lives inside the Kill Switch like everything else on the platform, so the identity's rest frame is the composition the sheet shows.
51
+
52
+ ## Step 6: the image direction is a hero-object family
53
+
54
+ The atmosphere the identity carries is a **family of hero plates and hero objects**: one object (the metaphor made physical) in the world's materials, whole, text-free, in the family's one light and one lens, in pairs with a sidecar beside every file, produced through `imagegen-frontend-web` under the pair-and-sidecar convention and the instance's art pipeline key. The chrome world's chrome, frost and gold renders of one bongo pair are the worked example of a family. Nothing in the family carries letters, UI, or a frame.
55
+
56
+ ## Step 7: the board is a contact sheet
57
+
58
+ The nine panels the upstream generated (logo cover, construction logic, digital application, essence tagline, colour system, typography specimen, physical application, image direction, system detail) become a **contact sheet** assembled from real parts: the drawn mark at three sizes, its construction comment, a kit shot of a surface, the pack's swatches from a specimen page, the type hierarchy from that page, a second surface, one plate from the family, and a detail crop of a real component. Rhythm across the sheet is kept as the upstream asked: quiet, then functional, then emotional, then technical, then atmospheric, then detailed, never every panel shouting at once. The sheet is a review artifact for the owner and lives under the claim's scratch or `docs/design/`, never under a surface's assets.
59
+
60
+ ## The reference rule
61
+
62
+ When references are supplied, extract only layout rhythm, grid, spacing, type scale, density, placement and accent logic, and **never reproduce a supplied logo, brand name, slogan, composition or unique asset**. Every board must be able to answer, in one line each: what the brand represents, what its core metaphor is, how the mark expresses that, how the system scales across surfaces, and why the whole thing is ownable. A system that cannot answer the last one is not finished.
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: image-to-code
3
+ description: The analyse-then-implement loop for visually important web work on ANY Cloud Bongos instance, under the owner's hero-only rule: the design source of truth for the UI is the owner's reference images and the instance's world, read with a structured per-image analysis and extracted into a full spec before a line is written; the only image this skill generates is the hero plate the composition has a slot for, and the sections are built from the world's templates. Implements as a faithful translation rather than an interpretation, names design drift as the failure mode, holds the hero and clutter rules, and ends on a clarity checklist the kit measures. Triggers on "build this from the reference", "recreate these images in code", "implement the mock faithfully", "the owner sent screenshots to build from", or when a claimed ui task carries reference images. Never generates a section, a comp or a screen. Ported in-house from the taste-skill rebuild spec (tasks 1003325 and 1003328, ADR 0198) with the hero-only rewrite the upstream never had.
4
+ ---
5
+
6
+ <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
7
+ > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
8
+ >
9
+ > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
10
+ > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
11
+ > 3. **A style skill produces variants INSIDE that world, never a second world.** The look this skill carries (its palette, faces, materials, mood boards) is a reference for composition and craft; the instance's tokens and faces replace it. Changing the world is the owner's decision and an ADR, not a session's taste.
12
+ > 4. **Images are for hero plates and hero objects only.** UI is built from templates and the instance's world, not painted; any image-generation step below applies to text-free plates and hero objects, never to controls, cards, text, or a screenshot of a screen.
13
+ > 5. **The instance's taste bans apply and win.** Read them from its `DESIGN.md`. Cloud Bongos's own (no em dash or en dash in visible copy, no emoji, no gradient text, no three-equal-card row, no fake screenshot built from divs) are that instance's, not the module's; where this skill's rules conflict with an instance's bans, the instance wins.
14
+ > 6. **The platform floors hold on every instance** and a `DESIGN.md` may tighten them, never loosen them: every control clears 24px on both sides (primary pills 46–48px); AA in both modes (4.5:1 body text, 3:1 large text and non-text) measured from the real stylesheet; and the Kill Switch, `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, no motion authored in JavaScript, and a rest frame that is a complete composition on its own.
15
+ > 7. **Look before you ship.** Render the real surface through the module's kit before and after (`/design` Step 2 and `docs/recipes/ui-look-before-you-ship.md`: every state at 1440 / 390 / 320 in dark and light, audited against the floors); a page with no states file gets one first, and new interactive code ships with a pin in the page's own test file.
16
+ >
17
+ > The rules below are the skill's own. A vendored copy carries a `PROVENANCE.md` beside this file naming the origin, the pinned commit, the licence and the scan verdict it was vendored under; an in-house rebuild names its rebuild spec and its ADR in the section right after this block.
18
+ <!-- END PLATFORM PREAMBLE -->
19
+
20
+ ## Rebuilt from
21
+
22
+ This skill is an **in-house rebuild**, not a vendored copy. Its documented functionality comes from `Leonxlnx/taste-skill` @ `<redacted>` (MIT; the `image-to-code` entry), which `/scan-before-install` reduced to `dangerous` at the default tier on 2026-08-28, so under [ADR 0198](../../../../docs/adr/<redacted>.md) it could land only as a REBUILD-SPEC. The spec is the `[image-to-code]` group in the body of [task 1003325](https://cloudbongos.com/builders#/task/1003325); the port is [task 1003328](https://cloudbongos.com/builders#/task/1003328); this file implements the requirements from scratch, **no upstream bytes consulted or copied**. The landing shape is [ADR 0220](../../../../docs/adr/<redacted>.md); the hero-only rewrite this family carries is [ADR 0222](../../../../docs/adr/<redacted>.md).
23
+
24
+ What changed in translation, on purpose: the upstream's mandatory order was **generate the design images, analyse them, implement**, with the generated image as the source of truth, one large render per section, and a ban on freeform coding whenever a generator was available. The owner's rule is the opposite for UI: image generation is for text-free hero plates and hero objects only, and an image model re-rendering a template costs credits and drifts, so **the owner's reference images and the instance's world are the source of truth**, the sections are rebuilt from the world's templates, and the one generated image is the hero plate the composition has a slot for (through `imagegen-frontend-web`). The analysis discipline, the extraction spec, the faithful-copy rule, the drift bans, the hero rules, the clutter bans and the clarity checklist all survive; they now read the references instead of a generated set. The eagerness mandate and the image-count rule are gone.
25
+
26
+ ## Scope: when this skill fires, and when it declines
27
+
28
+ **Fires** when a ui task comes with reference images (the owner's templates under `docs/design/references/<date>/`, a designer's export, a mock from the kit's `docs/` route) and the deliverable is code: a page, a band, a component built to match them.
29
+
30
+ **Declines, and says so:** a request to generate the design first ("make me an image of the page, then build it"), a request to render a section or a comp, a native surface. The nearest thing it does instead is Step 1 with what exists, and, where the composition has a slot, one plate.
31
+
32
+ ## Step 1: the order is read, extract, plate, implement
33
+
34
+ 1. **Read** the world (the preamble's order) and every reference image, with the analysis below, before any opinion.
35
+ 2. **Extract** the full spec from the references (Step 3).
36
+ 3. **Plate**: if the composition has a hero slot and the references show one, the plate is produced by `imagegen-frontend-web` under the pair-and-sidecar convention, through the instance's art pipeline key (the builder's own at the hall's settings or the instance's shared one), never a credential this skill carries; if the references show a section image, a screenshot or a comp, that element is rebuilt in code from the world's templates and the image is not generated.
37
+ 4. **Implement** as a faithful translation (Step 4), then prove it (Step 6).
38
+
39
+ A reference is never cropped or zoomed to get a detail: a crop destroys the spacing, the proportion and the type scale the analysis needs. Ask for a larger export, or measure what is there and say what stayed unclear.
40
+
41
+ ## Step 2: the structured per-image analysis
42
+
43
+ For every reference, in this order, written down before coding: **what the section is**; its **visual priority** on the page; the **readable text** (verbatim, so copy is never invented); the **type relationships** (how many sizes, their ratio, the weights, the tracking, the case); the **spacing** (gutters, band air, group tightness, in the world's scale); the **controls** (their shape, height, the radius tier, the hover the reference implies); the **card logic** (whether things are contained at all, and how); the **dominant colours** mapped onto the pack's roles, never as hexes; the **rhythm** (what repeats and what varies); and **what remains unclear**. A vibe read is not an analysis.
44
+
45
+ ## Step 3: the extraction spec
46
+
47
+ One document in the ship notes before the first edit: visible copy; the type scale and weights as roles on the pack's two faces; spacing and gutters on the world's scale; radii on the world's tier; the button hierarchy and padding; the palette and accents as pack roles; the background and image treatment (a field, an object, a plate slot, or none); the grid logic; the section order. Where the reference shows something the world bans (a gradient text, an eyebrow above the h1, a three-equal-card row, an em dash), the spec records it as *reference: yes, world: no* and the world wins.
48
+
49
+ ## Step 4: implement as a faithful translation
50
+
51
+ The layout logic, the spacing rhythm, the section order, the typographic mood and the component style of the reference are preserved; only the substance changes (the pack's colours and faces, the world's rules, real copy). **Drift is the failure mode**, named in the notes when it happens: simplifying into a template, compressing the reference's generous spacing, flattening its type hierarchy, or reintroducing a container nesting the analysis removed. Stack conventions are the platform's: static markup, one sheet, the pack spliced by the instance, live DOM by `createElement` and `textContent`, the design system is the instance's `DESIGN.md`, a variant of the look is one from `modules/ui-design/styles/` rendered with `--style <name>` on the kit.
52
+
53
+ ## Step 5: the hero and clutter rules
54
+
55
+ The hero holds one focal point (the plate or the headline), a one to three line headline, restrained support copy, and no badge, stat or pill stuffing; the first view stays readable and uncrowded on a small laptop (1440 by 900 is the kit's first shot; 1280 by 720 is the one to imagine). No nested box-in-box section wrappers. No micro-UI clutter: filler chips, pseudo-system markers, fake technical labels, meaningless metadata rows. Where a reference carries them, the spec records them and the build leaves them out, saying so.
56
+
57
+ ## Step 6: the clarity checklist, measured
58
+
59
+ - [ ] **Honest image count**: exactly the plate slots the composition has, each a text-free hero plate or hero object with its sidecar, and no other image was generated.
60
+ - [ ] **Text readability**: every string in the build is readable at 1440, 390 and 320, and every string came from the reference or the copy desk, never invented.
61
+ - [ ] **Extraction completeness**: the spec has no "unclear" left that the build guessed at; each was asked, measured, or recorded as an open decision.
62
+ - [ ] **Hero cleanliness**: one focal point, the line count held, the primary action above the fold in the first shot.
63
+ - [ ] **No generated tells**: no gradient text, no purple-blue, no glass for its own sake, no fake screenshot, no filler.
64
+ - [ ] **Cross-image coherence**: the bands built from different references read as one page under one world.
65
+ - [ ] **The kit**: `node modules/ui-design/kit/render.js --page <path>` ALL CLEAN in every mode the page serves, `probe.js` PASS, contrast measured over any plate; the reduced-motion pass dead under the Kill Switch.
66
+ - [ ] **The notes**: the spec, the drift list, and which shots were looked at in which modes.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: imagegen-frontend-mobile
3
+ description: The plate-making skill for the narrow end of a web surface on ANY Cloud Bongos instance, under the owner's hero-only rule: it produces text-free portrait plates and hero objects for the 390 and 320 compositions the kit renders, the masthead above the words and the field behind them, sized for the fold and clear of the regions a phone reserves. Never a screen, never a phone mockup, never a flow of screens as images, and never code: a screen concept is the surface's states file rendered by the kit at 390, and a native app target declines to /design. Locks one material, light and camera across the family from the world's DESIGN.md, keeps the first view to one focal point, puts a scrim under any text that sits over a plate, and lands the pair convention with a sidecar beside every file. Triggers on "a plate for the mobile hero", "the masthead at 390", "the hero object for the phone collapse", "a portrait plate", or when adapt or design-taste-frontend names a narrow-width slot with no plate. Ported in-house from the taste-skill rebuild spec (tasks 1003325 and 1003328, ADR 0198) with the hero-only rewrite the upstream never had.
4
+ ---
5
+
6
+ <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
7
+ > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
8
+ >
9
+ > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
10
+ > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
11
+ > 3. **A style skill produces variants INSIDE that world, never a second world.** The look this skill carries (its palette, faces, materials, mood boards) is a reference for composition and craft; the instance's tokens and faces replace it. Changing the world is the owner's decision and an ADR, not a session's taste.
12
+ > 4. **Images are for hero plates and hero objects only.** UI is built from templates and the instance's world, not painted; any image-generation step below applies to text-free plates and hero objects, never to controls, cards, text, or a screenshot of a screen.
13
+ > 5. **The instance's taste bans apply and win.** Read them from its `DESIGN.md`. Cloud Bongos's own (no em dash or en dash in visible copy, no emoji, no gradient text, no three-equal-card row, no fake screenshot built from divs) are that instance's, not the module's; where this skill's rules conflict with an instance's bans, the instance wins.
14
+ > 6. **The platform floors hold on every instance** and a `DESIGN.md` may tighten them, never loosen them: every control clears 24px on both sides (primary pills 46–48px); AA in both modes (4.5:1 body text, 3:1 large text and non-text) measured from the real stylesheet; and the Kill Switch, `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, no motion authored in JavaScript, and a rest frame that is a complete composition on its own.
15
+ > 7. **Look before you ship.** Render the real surface through the module's kit before and after (`/design` Step 2 and `docs/recipes/ui-look-before-you-ship.md`: every state at 1440 / 390 / 320 in dark and light, audited against the floors); a page with no states file gets one first, and new interactive code ships with a pin in the page's own test file.
16
+ >
17
+ > The rules below are the skill's own. A vendored copy carries a `PROVENANCE.md` beside this file naming the origin, the pinned commit, the licence and the scan verdict it was vendored under; an in-house rebuild names its rebuild spec and its ADR in the section right after this block.
18
+ <!-- END PLATFORM PREAMBLE -->
19
+
20
+ ## Rebuilt from
21
+
22
+ This skill is an **in-house rebuild**, not a vendored copy. Its documented functionality comes from `Leonxlnx/taste-skill` @ `<redacted>` (MIT; the `imagegen-frontend-mobile` entry), which `/scan-before-install` reduced to `dangerous` at the default tier on 2026-08-28, so under [ADR 0198](../../../../docs/adr/<redacted>.md) it could land only as a REBUILD-SPEC. The spec is the `[imagegen-frontend-mobile]` group in the body of [task 1003325](https://cloudbongos.com/builders#/task/1003325); the port is [task 1003328](https://cloudbongos.com/builders#/task/1003328); this file implements the requirements from scratch, **no upstream bytes consulted or copied**. The landing shape is [ADR 0220](../../../../docs/adr/<redacted>.md); the hero-only rewrite this family carries is [ADR 0222](../../../../docs/adr/<redacted>.md).
23
+
24
+ What changed in translation, on purpose, and it is the largest change in the family: the upstream produced **premium mobile app screen images**, one per screen, inside a phone mockup, as an ordered flow with an internal design bible. A screen image is a screenshot of a screen, which the preamble bans on every instance, and a phone mockup is a frame around one. So here the **screen is the surface itself**, rendered by the kit at 390 and 320 from its states file; the **flow** is the states file's order; the **design bible** is the instance's `DESIGN.md` and its pack; and what this skill generates is only what the narrow composition has a slot for: a portrait masthead, a field behind the fold, a small object in a row. The platform-mode choice (one mobile operating system or the other) becomes a decline: every surface here is a served web page, and a native app target routes to `/design`. The first-screen rule, the mobile realities, the scrim rule and the slop bans survive, applied to plates.
25
+
26
+ ## Scope: when this skill fires, and when it declines
27
+
28
+ **Fires** when the narrow composition of a surface has a plate slot: the masthead the world names above the words below its breakpoint (the chrome world's big blob becomes one at 900 and under), a field behind a 390 fold, an orb in a collapsed row, a closing figure on a phone-height band.
29
+
30
+ **Declines, and says so:** a screen image, a phone or device mockup, a flow of screens, an app icon, a splash screen with a wordmark, a native app for either mobile platform or cross-platform (that is `/design`, with one line: a browser capture is not evidence for a native surface). A request for "the onboarding screens as images" gets the states-file answer below and the reason.
31
+
32
+ ## Step 1: the screen is the states file, the bible is the world
33
+
34
+ Read the surface's states file (`<page>.states.json`) and the world's `DESIGN.md`; both exist before a plate does. A first run, an empty state or a sign-in step is a **state** the kit renders at 390 and 320, not an image; where the states file lacks one, add it (the `impeccable onboard` and `harden` playbooks own that work) and render, then look at the shots to see where a plate slot actually is. The bible the upstream asked for is already written: the pack's fifteen, the faces, the radius tier, the shadow language, the motion grammar. A plate that needs a rule the world does not have is a gap for the owner, not a rule this skill adds.
35
+
36
+ ## Step 2: commit to the narrow plate list out loud
37
+
38
+ ```
39
+ Narrow plates: <slot> · <aspect: 4:5 | 9:16 | 1:1> · <mode pair> · <material, from the family>
40
+ ```
41
+
42
+ One line per slot, never more; usually one. Say it, and put it in the ship notes. The family (material, key light, lens, ground) is the one the surface's wide plates already use; a narrow plate that reads as another studio is regenerated.
43
+
44
+ ## Step 3: the first view and the mobile realities
45
+
46
+ The first view at 390 by 844 holds **one focal point** (the masthead or the field, never both), a one to three line statement, concise support copy and one clear next action, and no chip, stat or pill stuffing; the plate is sized so all of that fits above the fold with the primary action visible. The plate keeps clear of what a phone reserves: the top system region, the bottom gesture region and the home indicator, the docked sheet zone; a masthead sits under the bar, not behind it. **Any text over a plate sits behind a scrim** mixed from black or white with alpha, and the pair is measured at 4.5:1 with the plate in place, in both modes.
47
+
48
+ ## Step 4: the prompt, the key, the run
49
+
50
+ A narrow plate is a text-free hero plate or hero object like any other, and the same rule holds: text-free hero plates and hero objects only. The prompt is the family's sentence plus the object, whole and centred, then the refusals every time: **no text, no letters, no logo, no watermark, no UI, no phone**. A portrait plate is generated at its portrait aspect, never cropped down from a landscape one, because the crop destroys the proportion the slot was sized for. Generation goes through the instance's art pipeline (the builder's own image key at the hall's settings, `/builders/settings#art-key`, or the instance's shared key); preflight the cost, generate at 2k, log the spend with `node scripts/gds/cost.js <usd> art --task-id <id>`; a white ground goes through the pipeline's background remover and the alpha is verified with `ffmpeg -vf alphaextract` before anything is called a cutout.
51
+
52
+ ## Step 5: the output contract
53
+
54
+ The same pair-and-sidecar convention as the wide plates: the black-ground plate as JPEG for dark, the alpha cutout as WebP for light, at production size and at least twice the display size, a `<file>.<ext>.json` sidecar beside every file (kind, model, aspect ratio, resolution, generated, round, the exact prompt, source, encoded, use), and `--plate-<name>` / `--plate-<name>-cut` tokens on `body` with root-absolute `url()`. A narrow plate that only serves the breakpoint is still a token on `body`, switched in the world's `@media` block, never a second sheet. The style library at `modules/ui-design/styles/` holds a look's derived recipes and brings no plates of its own until its owner asks; a surface's plates are the surface's.
55
+
56
+ ## Step 6: the bans, applied to plates
57
+
58
+ Refused by name: the purple-blue gradient look, purposeless glass and floating blobs with no slot, a fake chart or dashboard rendered into a plate, cloned plates across states, a phone-shaped anything, a plate with motivational copy or a brand name in it. A plate carries an object or an atmosphere and nothing that reads.
59
+
60
+ ## Step 7: the pre-flight
61
+
62
+ `node modules/ui-design/kit/render.js --page <path> --sizes 390,320` with the plates in place: ALL CLEAN in every mode the page serves, the fold checked in the first shot of each state, nothing scrolling sideways, the 24px floor holding on every control the plate sits near, the reduced-motion pass dead (the Kill Switch keeps the rest frame the composition you judged). A weak plate goes back for regeneration, not for a crop. The ship notes carry the plate list, the spend, and the shots looked at.
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: imagegen-frontend-web
3
+ description: The plate-making skill for a web surface on ANY Cloud Bongos instance, under the owner's hero-only rule: it produces text-free hero plates, hero objects and atmosphere backdrops for the slots a composition already has, never one image per section, never a screen, never a UI comp, and it writes no page code. Commits to the plate list out loud, chooses a composition anchor per plate that is not the text-left image-right default, locks one material, one light and one camera across the family, matches every plate tonally to the instance's pack, runs the generation through the instance's art pipeline key, and lands each plate as the pair convention (a black-ground plate for dark, an alpha cutout for light) with a provenance sidecar beside every file. Triggers on "make a hero plate", "we need a hero object for", "an atmosphere backdrop for the landing", "the project orbs need a new material", or when design-taste-frontend or /design names a plate slot with no plate. Ported in-house from the taste-skill rebuild spec (tasks 1003325 and 1003328, ADR 0198) with the hero-only rewrite the upstream never had.
4
+ ---
5
+
6
+ <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
7
+ > **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
8
+ >
9
+ > 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
10
+ > 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
11
+ > 3. **A style skill produces variants INSIDE that world, never a second world.** The look this skill carries (its palette, faces, materials, mood boards) is a reference for composition and craft; the instance's tokens and faces replace it. Changing the world is the owner's decision and an ADR, not a session's taste.
12
+ > 4. **Images are for hero plates and hero objects only.** UI is built from templates and the instance's world, not painted; any image-generation step below applies to text-free plates and hero objects, never to controls, cards, text, or a screenshot of a screen.
13
+ > 5. **The instance's taste bans apply and win.** Read them from its `DESIGN.md`. Cloud Bongos's own (no em dash or en dash in visible copy, no emoji, no gradient text, no three-equal-card row, no fake screenshot built from divs) are that instance's, not the module's; where this skill's rules conflict with an instance's bans, the instance wins.
14
+ > 6. **The platform floors hold on every instance** and a `DESIGN.md` may tighten them, never loosen them: every control clears 24px on both sides (primary pills 46–48px); AA in both modes (4.5:1 body text, 3:1 large text and non-text) measured from the real stylesheet; and the Kill Switch, `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, no motion authored in JavaScript, and a rest frame that is a complete composition on its own.
15
+ > 7. **Look before you ship.** Render the real surface through the module's kit before and after (`/design` Step 2 and `docs/recipes/ui-look-before-you-ship.md`: every state at 1440 / 390 / 320 in dark and light, audited against the floors); a page with no states file gets one first, and new interactive code ships with a pin in the page's own test file.
16
+ >
17
+ > The rules below are the skill's own. A vendored copy carries a `PROVENANCE.md` beside this file naming the origin, the pinned commit, the licence and the scan verdict it was vendored under; an in-house rebuild names its rebuild spec and its ADR in the section right after this block.
18
+ <!-- END PLATFORM PREAMBLE -->
19
+
20
+ ## Rebuilt from
21
+
22
+ This skill is an **in-house rebuild**, not a vendored copy. Its documented functionality comes from `Leonxlnx/taste-skill` @ `<redacted>` (MIT; the `imagegen-frontend-web` entry), which `/scan-before-install` reduced to `dangerous` at the default tier on 2026-08-28, so under [ADR 0198](../../../../docs/adr/<redacted>.md) it could land only as a REBUILD-SPEC. The spec is the `[imagegen-frontend-web]` group in the body of [task 1003325](https://cloudbongos.com/builders#/task/1003325); the port is [task 1003328](https://cloudbongos.com/builders#/task/1003328); this file implements the requirements from scratch, **no upstream bytes consulted or copied**. The landing shape is [ADR 0220](../../../../docs/adr/<redacted>.md); the hero-only rewrite this family carries is [ADR 0222](../../../../docs/adr/<redacted>.md).
23
+
24
+ What changed in translation, on purpose: the upstream produced **one horizontal design-reference image per section**, scaled the image count with the section count, refused to stop early, and treated imagery as a material several sections had to carry. The owner's rule (2026-08-26, reaffirmed 2026-08-27) is that image generation is for **text-free hero plates and hero objects only**; UI is rebuilt from templates and the instance's world, never painted. So here the count is the number of plate slots the composition already has (usually one to three), a plate never carries a section, a headline, a control or a screen, and the panel's eagerness mandate, its image-count mandate and its must-invoke-any-tool directive are gone. The composition-variety rules, the palette lock and the cross-frame consistency survive and now govern a family of plates rather than a set of frames. The palette lock is the pack; the key is the instance's art pipeline; the output contract is DESIGN.md's pair-and-sidecar convention.
25
+
26
+ ## Scope: when this skill fires, and when it declines
27
+
28
+ **Fires** when a surface's composition has a plate slot: a hero field behind the type, a hero object beside or above the words, an atmosphere backdrop, a project orb, a closing figure. The slot exists first (named by the surface's `DESIGN.md`, a brief, or a taste skill); this skill fills it.
29
+
30
+ **Declines, and says so:** a section image, a product screenshot, a UI comp or mock, a phone frame, a logo or wordmark, a chart, a card, anything with letters in it, and any request whose real need is a layout (that is `design-taste-frontend` or `/design`). A brief that asks for "an image for every section" gets the plate list below and the reason, not the images.
31
+
32
+ ## Step 1: read the materials, then commit to the plate list out loud
33
+
34
+ Read the instance's `DESIGN.md` Materials section (the chrome world's plates table is the worked example: a token per plate, shipped in pairs, a sidecar beside every file) and the surface's own sheet for its `--plate-*` tokens. Then write the list before generating anything:
35
+
36
+ ```
37
+ Plates: <slot> · <aspect> · <mode pair: plate+cutout | cutout only> · <material>
38
+ ```
39
+
40
+ One line per slot, never more lines than slots. Say it to the person you are working with, and put it in the ship notes. A plate with no slot is the floating blob the audit rejects.
41
+
42
+ ## Step 2: the composition anchor, per plate
43
+
44
+ The text-left image-right hero is the most overused default and is refused by name. Choose one of these anchors for each plate, and do not repeat one more than twice in a row across a surface:
45
+
46
+ | Anchor | What it is | Where the type sits |
47
+ |---|---|---|
48
+ | **Field** | a full-bleed plate under the whole hero | over the plate, behind a scrim where it needs one |
49
+ | **Beside, bleeding** | a whole object past the container by a gutter or more | in the remaining measure |
50
+ | **Masthead** | the object above the words, offset from centre | under it; at 390 this is what every anchor collapses to |
51
+ | **Orb** | a small whole object in a row or a ledger | beside it, in the row's own type |
52
+ | **Backdrop** | atmosphere with no object, far behind the content | anywhere; the backdrop never competes |
53
+
54
+ Choose **one hero scale for the whole surface** (giant statement, mid editorial, mini minimalist) and let the plates below the hero vary in ambition so the scroll reads as rhythm, not as slabs of the same size.
55
+
56
+ ## Step 3: art direction from the brief, and one family across the surface
57
+
58
+ Map the brief's words to direction: a **minimalist** brief suspends the field and the backdrop and keeps one small object; an **editorial** brief raises the contrast between the object and the ground and allows an off-grid offset; a **cinematic** brief favours a graded field with the object low in it. Then **lock the family**: one material, one key light, one lens, one ground, stated in one sentence and repeated in every prompt (the chrome world's is "mirror-polished liquid chrome, a soft top-left key, a slightly long lens, pure black"). Variation is allowed only in the object, the anchor and the size. A plate that reads as a different studio is regenerated, not shipped.
59
+
60
+ ## Step 4: the palette lock is the pack
61
+
62
+ Every plate is tonally matched to the instance's fifteen: the object's own hues sit inside the pack's accent and second voice, the ground is the pack's dark (pure black where the world's dark is black) or is removed for the cutout, and nothing in the plate invents a colour the page will have to answer. Text over a plate sits behind a scrim mixed from black or white with alpha, never a coloured overlay. Gradients inside a plate may be tonal, single-hue or noise-textured; the rainbow mesh, the purple-to-blue, the pink-to-orange and the glow halo are banned in the prompt as they are in the sheet, and gradient text does not exist here because text does not exist here.
63
+
64
+ ## Step 5: the prompt discipline
65
+
66
+ One prompt per plate, written in this order: the object, whole and centred; the material; the light; the lens; the ground ("pure black everywhere the object is not", which is what makes the lighten-blend composite and the cutout both work); then the refusals, every time: **no text, no letters, no logo, no watermark, no UI**. A supplied reference is read for material, light and composition and never reproduced; a plate is never a crop or a zoom of an earlier plate, because a crop destroys the proportion the slot was sized for. Regenerate instead.
67
+
68
+ ## Step 6: the run, through the instance's key
69
+
70
+ Generation goes through the instance's art pipeline: the builder's own image key at the hall's settings (`/builders/settings#art-key`) or the instance's shared key, never a credential this skill carries or a service it installs. Preflight the cost, generate at 2k, and log the spend with `node scripts/gds/cost.js <usd> art --task-id <id>` under the claim. The model ignores the black-ground instruction about one time in six and returns a white ground; a plate that must sit on a non-black ground goes through the pipeline's background remover for a real-alpha cutout, and the alpha is verified with `ffmpeg -vf alphaextract` (a `signalstats` YAVG strictly between 0 and 255 is real alpha; an image viewer that drops alpha makes a good cutout look opaque).
71
+
72
+ ## Step 7: the output contract (the pair and the sidecar)
73
+
74
+ - **Sources** stay in the working directory (`.impeccable/mocks/<surface>-assets/` or the claim's scratch), never shipped.
75
+ - **What ships** goes under the surface's assets (`modules/<key>/public/assets/<world>/` on this tree; `assets/ui/<surface>/` under the contract's layout): the black-ground plate as **JPEG** (`ffmpeg -q:v 3`) for dark, and the cutout as **WebP with alpha** (`ffmpeg -c:v libwebp -quality 88 -pix_fmt yuva420p`) at 1600px for light, at production size and at least twice the display size, never at a mockup's crop size.
76
+ - **A sidecar beside every file**, named with the extension (`<name>.jpg.json`, `<name>-cut.webp.json`): `kind` (plate or cutout), `model`, `aspect_ratio`, `resolution`, `generated`, `round`, the exact `prompt`, `source`, `encoded`, and `use` (which mode and slot it serves). A raster with no sidecar does not ship.
77
+ - **Tokens, not paths, in the sheet**: `--plate-<name>` and `--plate-<name>-cut` on `body`, root-absolute `url(/assets/…)` because a custom property's `url()` resolves against the document; dark takes the plate, light the cutout, written in the dark twins like every other dark rule.
78
+
79
+ This skill lands the assets, the sidecars and the token names, and stops. Wiring the slot is the surface's taste skill or `/design`; the style library at `modules/ui-design/styles/` is where a plate family for a *look* would live, and a look brings none until its owner asks.
80
+
81
+ ## Step 8: the variety audit and the pre-flight
82
+
83
+ Lay the family out as a contact sheet and reject the set when: the same anchor repeats more than twice in a row; the same ground mode repeats across every plate; a plate count exceeds the slot count; a plate carries text, a control or a screen; the material drifts between plates. Then the kit: `node modules/ui-design/kit/render.js --page <path>` with the plates in place, ALL CLEAN in every mode the page serves, and the contrast of any text over a plate measured at every phase where the plate moves (the Kill Switch keeps the rest frame the composition you judged). The ship notes carry the plate list, the anchors chosen, the spend, and the shots looked at.