luckiest-co 1.0.13 → 1.0.14

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 (30) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/package.json +1 -1
  4. package/skills/luckiest-design-website/ATTRIBUTION.md +38 -0
  5. package/skills/luckiest-design-website/CHANGELOG.md +104 -0
  6. package/skills/luckiest-design-website/LICENSE +21 -0
  7. package/skills/luckiest-design-website/SKILL.md +372 -0
  8. package/skills/luckiest-design-website/UPSTREAM-CHANGELOG.md +526 -0
  9. package/skills/luckiest-design-website/engine/scrollcraft.css +432 -0
  10. package/skills/luckiest-design-website/engine/scrollcraft.js +1167 -0
  11. package/skills/luckiest-design-website/evals/evals.json +33 -0
  12. package/skills/luckiest-design-website/references/assets.md +286 -0
  13. package/skills/luckiest-design-website/references/device-diag.html +214 -0
  14. package/skills/luckiest-design-website/references/devices.md +466 -0
  15. package/skills/luckiest-design-website/references/feel.md +277 -0
  16. package/skills/luckiest-design-website/references/registry-examples.md +305 -0
  17. package/skills/luckiest-design-website/references/taste.md +304 -0
  18. package/skills/luckiest-design-website/references/template.html +138 -0
  19. package/skills/luckiest-design-website/references/uniqueness.md +480 -0
  20. package/skills/luckiest-design-website/references/verify.md +381 -0
  21. package/skills/luckiest-design-website/references/worldflight.md +349 -0
  22. package/skills/luckiest-design-website/references/worlds.md +178 -0
  23. package/skills/luckiest-design-website/scripts/doctor.mjs +177 -0
  24. package/skills/luckiest-design-website/scripts/encode.sh +80 -0
  25. package/skills/luckiest-design-website/scripts/kie.mjs +202 -0
  26. package/skills/luckiest-design-website/scripts/serve.mjs +52 -0
  27. package/skills/luckiest-design-website/scripts/shoot.mjs +644 -0
  28. package/skills/luckiest-design-website/scripts/workspace.mjs +106 -0
  29. package/skills/luckiest-design-website/scripts/worldflight-assert.mjs +273 -0
  30. package/skills/luckiest-design-website/templates/FINGERPRINTS.md +65 -0
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Resolve the scrollcraft workspace: the one directory that holds this user's
4
+ * builds and their fingerprint registry.
5
+ *
6
+ * The skill used to hardcode one author's repo layout, which meant the
7
+ * fingerprint gate dead-ended on every other machine. Nothing is hardcoded now.
8
+ * Resolution order, first hit wins:
9
+ *
10
+ * 1. SCROLLCRAFT_HOME env var, absolute or relative to cwd
11
+ * 2. .scrollcraft.json nearest one walking up from cwd,
12
+ * { "workspace": "some/path" } relative to
13
+ * the file that declares it
14
+ * 3. <project root>/scrollcraft project root = nearest ancestor with .git,
15
+ * otherwise cwd
16
+ *
17
+ * USAGE
18
+ * node scripts/workspace.mjs print the workspace path
19
+ * node scripts/workspace.mjs --json print every resolved path
20
+ * node scripts/workspace.mjs --ensure create it and seed the registry
21
+ */
22
+
23
+ import fs from "node:fs";
24
+ import path from "node:path";
25
+ import { fileURLToPath } from "node:url";
26
+
27
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
28
+ const SKILL = path.resolve(HERE, "..");
29
+
30
+ function findUp(startDir, predicate) {
31
+ let dir = path.resolve(startDir);
32
+ for (let i = 0; i < 12; i++) {
33
+ const hit = predicate(dir);
34
+ if (hit) return { dir, hit };
35
+ const up = path.dirname(dir);
36
+ if (up === dir) break;
37
+ dir = up;
38
+ }
39
+ return null;
40
+ }
41
+
42
+ export function resolveWorkspace(cwd = process.cwd()) {
43
+ if (process.env.SCROLLCRAFT_HOME) {
44
+ return { root: path.resolve(cwd, process.env.SCROLLCRAFT_HOME), via: "SCROLLCRAFT_HOME" };
45
+ }
46
+
47
+ const cfg = findUp(cwd, (d) => {
48
+ const p = path.join(d, ".scrollcraft.json");
49
+ return fs.existsSync(p) ? p : null;
50
+ });
51
+ if (cfg) {
52
+ let parsed = {};
53
+ try {
54
+ parsed = JSON.parse(fs.readFileSync(cfg.hit, "utf8"));
55
+ } catch (e) {
56
+ throw new Error(`.scrollcraft.json is not valid JSON: ${cfg.hit}\n ${e.message}`);
57
+ }
58
+ if (parsed.workspace) {
59
+ return { root: path.resolve(cfg.dir, parsed.workspace), via: cfg.hit };
60
+ }
61
+ }
62
+
63
+ const git = findUp(cwd, (d) => (fs.existsSync(path.join(d, ".git")) ? d : null));
64
+ const base = git ? git.hit : path.resolve(cwd);
65
+ return { root: path.join(base, "scrollcraft"), via: git ? "project root (.git)" : "cwd" };
66
+ }
67
+
68
+ export function paths(cwd = process.cwd()) {
69
+ const { root, via } = resolveWorkspace(cwd);
70
+ return {
71
+ via,
72
+ workspace: root,
73
+ builds: path.join(root, "builds"),
74
+ lab: path.join(root, "lab"),
75
+ fingerprints: path.join(root, "FINGERPRINTS.md"),
76
+ };
77
+ }
78
+
79
+ export function ensure(cwd = process.cwd()) {
80
+ const p = paths(cwd);
81
+ fs.mkdirSync(p.builds, { recursive: true });
82
+ fs.mkdirSync(p.lab, { recursive: true });
83
+ let seeded = false;
84
+ if (!fs.existsSync(p.fingerprints)) {
85
+ const seed = path.join(SKILL, "templates", "FINGERPRINTS.md");
86
+ fs.copyFileSync(seed, p.fingerprints);
87
+ seeded = true;
88
+ }
89
+ return { ...p, seeded };
90
+ }
91
+
92
+ // ------------------------------------------------------------------- cli ----
93
+ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
94
+ const argv = process.argv.slice(2);
95
+ const p = argv.includes("--ensure") ? ensure() : paths();
96
+ if (argv.includes("--json")) {
97
+ console.log(JSON.stringify(p, null, 2));
98
+ } else if (argv.includes("--ensure")) {
99
+ console.log(p.workspace);
100
+ console.error(` builds ${p.builds}`);
101
+ console.error(` registry ${p.fingerprints}${p.seeded ? " (seeded, empty)" : " (already present)"}`);
102
+ console.error(` resolved via ${p.via}`);
103
+ } else {
104
+ console.log(p.workspace);
105
+ }
106
+ }
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Worldflight rig assertions. Not a contact sheet: these are the mechanical
4
+ * claims the mode makes, checked one at a time.
5
+ *
6
+ * node lab/worldflight-assert.mjs --url http://localhost:4520
7
+ */
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import { createRequire } from "node:module";
11
+ const { chromium } = createRequire(path.join(process.cwd(), "package.json"))("playwright-core");
12
+
13
+ const argv = process.argv.slice(2);
14
+ const arg = (n, d) => { const i = argv.indexOf(n); return i > -1 && argv[i + 1] ? argv[i + 1] : d; };
15
+ const URL = arg("--url", "http://localhost:4520");
16
+
17
+ const CHROME = [
18
+ process.env.SCROLLCRAFT_CHROME,
19
+ // Windows
20
+ "C:/Program Files/Google/Chrome/Application/chrome.exe",
21
+ "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe",
22
+ "C:/Program Files/Microsoft/Edge/Application/msedge.exe",
23
+ // macOS
24
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
25
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
26
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
27
+ // Linux
28
+ "/usr/bin/google-chrome",
29
+ "/usr/bin/google-chrome-stable",
30
+ "/usr/bin/chromium",
31
+ "/usr/bin/chromium-browser",
32
+ "/snap/bin/chromium",
33
+ ].find((p) => p && fs.existsSync(p));
34
+
35
+ let pass = 0, fail = 0;
36
+ const ok = (name, cond, note = "") => {
37
+ if (cond) { pass++; console.log(` PASS ${name}${note ? " " + note : ""}`); }
38
+ else { fail++; console.log(` FAIL ${name}${note ? " " + note : ""}`); }
39
+ };
40
+
41
+ const browser = await chromium.launch({ executablePath: CHROME, headless: true });
42
+
43
+ // ============================================================ desktop ======
44
+ {
45
+ const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 });
46
+ const errs = [];
47
+ page.on("console", (m) => { if (m.type() === "error") errs.push(m.text()); });
48
+ page.on("pageerror", (e) => errs.push(String(e)));
49
+ await page.goto(URL, { waitUntil: "domcontentloaded" });
50
+ await page.waitForSelector("html.sc-ready", { timeout: 15000 });
51
+ await page.evaluate(() => document.fonts.ready);
52
+ await page.waitForTimeout(900);
53
+
54
+ const geom = await page.evaluate(() => {
55
+ const segs = [...document.querySelectorAll("[data-sc-segment]")];
56
+ const total = segs.reduce((s, el) => s + (parseFloat(el.dataset.scW) || 1.3), 0);
57
+ const spacer = document.querySelector("[data-sc-spacer]");
58
+ const stage = document.querySelector("[data-sc-world]");
59
+ return {
60
+ vh: innerHeight, total,
61
+ weights: segs.map((el) => parseFloat(el.dataset.scW) || 1.3),
62
+ spacerH: spacer.getBoundingClientRect().height,
63
+ wantH: Math.round((total + 1) * innerHeight),
64
+ docH: document.body.scrollHeight,
65
+ stagePos: getComputedStyle(stage).position,
66
+ stageRect: (({ top, left, width, height }) => ({ top, left, width, height }))(stage.getBoundingClientRect()),
67
+ copyPos: getComputedStyle(document.querySelector("[data-sc-world-copy]")).position,
68
+ seam: parseFloat(document.querySelector('[data-sc-mode="worldflight"]').dataset.scSeam),
69
+ };
70
+ });
71
+ console.log(`\nDESKTOP vh=${geom.vh} weights=[${geom.weights}] total=${geom.total}vh`);
72
+
73
+ ok("spacer height = (sum weights + 1) x vh",
74
+ Math.abs(geom.spacerH - geom.wantH) <= 1, `got ${Math.round(geom.spacerH)}px want ${geom.wantH}px`);
75
+ ok("document scroll height is the spacer",
76
+ Math.abs(geom.docH - geom.wantH) <= 2, `doc ${geom.docH}px`);
77
+ ok("stage is position:fixed and fills the viewport",
78
+ geom.stagePos === "fixed" && geom.stageRect.height === geom.vh && geom.stageRect.top === 0);
79
+ ok("copy layer is position:fixed", geom.copyPos === "fixed");
80
+
81
+ // Nothing in flow but the spacer: at any scroll position, the only static or
82
+ // relative element that extends past the fold is the spacer and its ancestors.
83
+ const flow = await page.evaluate(() => {
84
+ const bad = [];
85
+ document.querySelectorAll("body *").forEach((el) => {
86
+ const cs = getComputedStyle(el);
87
+ if (cs.position === "fixed" || cs.position === "absolute") return;
88
+ if (el.hasAttribute("data-sc-spacer") || el.hasAttribute("data-sc-mode")) return;
89
+ const r = el.getBoundingClientRect();
90
+ if (r.height > 4 && r.bottom > innerHeight + 4) {
91
+ bad.push(el.tagName + "." + (el.className || "").toString().slice(0, 30));
92
+ }
93
+ });
94
+ return bad;
95
+ });
96
+ ok("nothing in document flow but the spacer", flow.length === 0, flow.join(", "));
97
+
98
+ // ---- lerp convergence, inside one leg ----------------------------------
99
+ const seg0 = geom.weights[0];
100
+ const A = Math.round(0.10 * seg0 * geom.vh);
101
+ const B = Math.round(0.85 * seg0 * geom.vh);
102
+ await page.evaluate((y) => scrollTo({ top: y, behavior: "instant" }), A);
103
+ await page.waitForTimeout(1400);
104
+
105
+ const trace = await page.evaluate(({ y }) => new Promise((res) => {
106
+ const v = document.querySelectorAll("[data-sc-segment] video")[0];
107
+ const clip = window.__sc.clips.find((c) => c.el === v);
108
+ const s = [];
109
+ scrollTo({ top: y, behavior: "instant" });
110
+ let n = 0;
111
+ (function f() {
112
+ s.push(+v.currentTime.toFixed(4));
113
+ if (++n < 70) requestAnimationFrame(f);
114
+ else res({ s, target: clip.target * (v.duration || 1), dur: v.duration, lerp: clip.lerp });
115
+ })();
116
+ }), { y: B });
117
+
118
+ const seq = trace.s;
119
+ const start = seq[0], end = seq[seq.length - 1];
120
+ const distinct = [...new Set(seq)];
121
+ const monotone = seq.every((v, i) => i === 0 || v >= seq[i - 1] - 1e-6);
122
+ const overshoot = Math.max(...seq) - trace.target;
123
+ console.log(` playhead ${start.toFixed(3)}s -> ${end.toFixed(3)}s target ${trace.target.toFixed(3)}s lerp ${trace.lerp} ${distinct.length} distinct values`);
124
+ ok("playhead is lerped, not written 1:1", distinct.length >= 4, `${distinct.length} distinct steps across 70 frames`);
125
+ ok("playhead is strictly monotone toward its target", monotone);
126
+ ok("playhead does not overshoot", overshoot <= 0.02, `max excess ${overshoot.toFixed(4)}s`);
127
+ ok("playhead converges on the target", Math.abs(end - trace.target) < 0.05,
128
+ `residual ${Math.abs(end - trace.target).toFixed(4)}s`);
129
+ ok("lerp rate is the 0.18 default", Math.abs(trace.lerp - 0.18) < 1e-9);
130
+
131
+ // ---- deadband: no seek thrash while the page is still -------------------
132
+ await page.waitForTimeout(900);
133
+ const seeks = await page.evaluate(() => new Promise((res) => {
134
+ let n = 0;
135
+ const vs = [...document.querySelectorAll("video")];
136
+ const h = () => n++;
137
+ vs.forEach((v) => v.addEventListener("seeked", h));
138
+ setTimeout(() => { vs.forEach((v) => v.removeEventListener("seeked", h)); res(n); }, 1000);
139
+ }));
140
+ ok("deadband holds: no seeks over 1s of stillness", seeks === 0, `${seeks} seeked events`);
141
+
142
+ // ---- crossfade band ----------------------------------------------------
143
+ const boundary = seg0 * geom.vh;
144
+ const half = (geom.seam / 2) * geom.vh;
145
+ const band = [];
146
+ for (let i = 0; i < 5; i++) {
147
+ const y = Math.round(boundary - half + (2 * half) * (i / 4));
148
+ await page.evaluate((y) => scrollTo({ top: y, behavior: "instant" }), y);
149
+ await page.waitForTimeout(120);
150
+ band.push(await page.evaluate(() => {
151
+ const segs = [...document.querySelectorAll("[data-sc-segment]")];
152
+ return segs.map((s) => +(parseFloat(getComputedStyle(s).opacity) || 0).toFixed(4));
153
+ }));
154
+ }
155
+ const incoming = band.map((b) => b[1]);
156
+ const outgoing = band.map((b) => b[0]);
157
+ console.log(` seam band incoming=[${incoming}] outgoing=[${outgoing}]`);
158
+ ok("incoming leg opacity is strictly monotone across the seam",
159
+ incoming.every((v, i) => i === 0 || v > incoming[i - 1]));
160
+ ok("incoming leg starts at 0 and ends at 1", incoming[0] <= 0.02 && incoming[4] >= 0.98);
161
+ // The outgoing leg must hold at full strength for the whole dissolve and only
162
+ // release once the incoming one covers it completely. A crossfade where BOTH
163
+ // sides are partly transparent shows the page ground through the middle of
164
+ // the seam, which is the flash this mode exists to remove.
165
+ ok("outgoing leg holds full opacity while the incoming one is still arriving",
166
+ incoming.every((v, i) => v >= 0.999 || outgoing[i] >= 0.999), `[${outgoing}]`);
167
+ ok("outgoing leg only releases once it is fully covered",
168
+ outgoing[4] === 0 && incoming[4] >= 0.999);
169
+
170
+ // ---- copy transform cap ------------------------------------------------
171
+ const maxT = [];
172
+ for (let i = 0; i <= 12; i++) {
173
+ const y = Math.round((geom.docH - geom.vh) * (i / 12));
174
+ await page.evaluate((y) => scrollTo({ top: y, behavior: "instant" }), y);
175
+ await page.waitForTimeout(60);
176
+ maxT.push(await page.evaluate(() => {
177
+ let m = 0;
178
+ document.querySelectorAll("[data-sc-copy]").forEach((el) => {
179
+ const t = new DOMMatrixReadOnly(getComputedStyle(el).transform);
180
+ m = Math.max(m, Math.abs(t.m42), Math.abs(t.m41));
181
+ });
182
+ return +m.toFixed(2);
183
+ }));
184
+ }
185
+ const worstT = Math.max(...maxT);
186
+ ok("copy never translates past 4vh", worstT <= geom.vh * 0.04 + 1,
187
+ `worst ${worstT}px, cap ${(geom.vh * 0.04).toFixed(0)}px`);
188
+
189
+ // ---- every leg reaches full opacity, every clip leaves its poster -------
190
+ const reach = {};
191
+ for (let i = 0; i <= 24; i++) {
192
+ const y = Math.round((geom.docH - geom.vh) * (i / 24));
193
+ await page.evaluate((y) => scrollTo({ top: y, behavior: "instant" }), y);
194
+ await page.waitForTimeout(260);
195
+ const s = await page.evaluate(() => [...document.querySelectorAll("[data-sc-segment]")].map((el, i) => ({
196
+ i, op: parseFloat(getComputedStyle(el).opacity) || 0,
197
+ painted: el.classList.contains("sc-has-clip"),
198
+ })));
199
+ s.forEach((x) => {
200
+ reach[x.i] = reach[x.i] || { op: 0, painted: false };
201
+ reach[x.i].op = Math.max(reach[x.i].op, x.op);
202
+ reach[x.i].painted = reach[x.i].painted || x.painted;
203
+ });
204
+ }
205
+ ok("every leg reaches full opacity",
206
+ Object.values(reach).every((r) => r.op >= 0.99),
207
+ Object.entries(reach).map(([i, r]) => `${i}:${r.op.toFixed(2)}`).join(" "));
208
+ ok("every leg paints a real frame (no clip stuck on poster)",
209
+ Object.values(reach).every((r) => r.painted),
210
+ Object.entries(reach).map(([i, r]) => `${i}:${r.painted ? "clip" : "POSTER"}`).join(" "));
211
+
212
+ ok("no console errors", errs.length === 0, errs.slice(0, 3).join(" | "));
213
+ await page.close();
214
+ }
215
+
216
+ // ==================================================== reduced motion =======
217
+ {
218
+ const page = await browser.newPage({
219
+ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1, reducedMotion: "reduce",
220
+ });
221
+ const media = [];
222
+ page.on("request", (r) => { if (/\.(mp4|webm)(\?|$)/.test(r.url())) media.push(r.url()); });
223
+ await page.goto(URL, { waitUntil: "domcontentloaded" });
224
+ await page.waitForSelector("html.sc-ready", { timeout: 15000 });
225
+ await page.waitForTimeout(800);
226
+ console.log("\nREDUCED MOTION");
227
+
228
+ const rmSeen = { legs: {}, copy: {} };
229
+ for (let i = 0; i <= 20; i++) {
230
+ const h = await page.evaluate(() => document.body.scrollHeight - innerHeight);
231
+ await page.evaluate((y) => scrollTo({ top: y, behavior: "instant" }), Math.round(h * (i / 20)));
232
+ await page.waitForTimeout(90);
233
+ const s = await page.evaluate(() => ({
234
+ legs: [...document.querySelectorAll("[data-sc-segment]")].map((el) => ({
235
+ op: parseFloat(getComputedStyle(el).opacity) || 0,
236
+ poster: (() => { const p = el.querySelector(".sc-world__poster"); return p ? getComputedStyle(p).transform : "none"; })(),
237
+ })),
238
+ copy: [...document.querySelectorAll("[data-sc-copy]")].map((el) => ({
239
+ t: (el.textContent || "").trim().replace(/\s+/g, " ").slice(0, 24),
240
+ op: parseFloat(getComputedStyle(el).opacity) || 0,
241
+ tf: getComputedStyle(el).transform,
242
+ })),
243
+ }));
244
+ s.legs.forEach((l, k) => {
245
+ rmSeen.legs[k] = rmSeen.legs[k] || { op: 0, tf: new Set() };
246
+ rmSeen.legs[k].op = Math.max(rmSeen.legs[k].op, l.op);
247
+ rmSeen.legs[k].tf.add(l.poster);
248
+ });
249
+ s.copy.forEach((c) => {
250
+ rmSeen.copy[c.t] = rmSeen.copy[c.t] || { op: 0, tf: new Set() };
251
+ rmSeen.copy[c.t].op = Math.max(rmSeen.copy[c.t].op, c.op);
252
+ rmSeen.copy[c.t].tf.add(c.tf);
253
+ });
254
+ }
255
+ await page.waitForTimeout(500);
256
+
257
+ ok("no clip is ever fetched", media.length === 0, media.join(", "));
258
+ ok("every leg's poster still reaches full opacity",
259
+ Object.values(rmSeen.legs).every((l) => l.op >= 0.99),
260
+ Object.entries(rmSeen.legs).map(([i, l]) => `${i}:${l.op.toFixed(2)}`).join(" "));
261
+ ok("no poster transform",
262
+ Object.values(rmSeen.legs).every((l) => [...l.tf].every((t) => t === "none")));
263
+ ok("every copy block still reaches full opacity",
264
+ Object.values(rmSeen.copy).every((c) => c.op >= 0.99),
265
+ Object.entries(rmSeen.copy).map(([t, c]) => `${c.op.toFixed(2)} "${t}"`).join(" | "));
266
+ ok("no copy transform",
267
+ Object.values(rmSeen.copy).every((c) => [...c.tf].every((t) => t === "none")));
268
+ await page.close();
269
+ }
270
+
271
+ await browser.close();
272
+ console.log(`\n${pass} passed, ${fail} failed`);
273
+ process.exit(fail ? 1 : 0);
@@ -0,0 +1,65 @@
1
+ # Fingerprints
2
+
3
+ Every site you build with **luckiest-design-website** gets one row here, appended after it
4
+ ships. The registry exists so your next build can prove it is a different page
5
+ rather than a re-skin of one you already made.
6
+
7
+ This file is **yours**. It starts empty on purpose: the gate is about not
8
+ repeating *yourself*, so it has nothing to say until you have built something.
9
+
10
+ The rules and the gate live in the skill's
11
+ `references/uniqueness.md`. Short version:
12
+
13
+ **A new build must differ from EVERY row below on at least 4 of the 6
14
+ dimensions.** Four against each row individually, not four on average across the
15
+ table. If a planned build fails, change the plan. Never edit a row to make room
16
+ for it.
17
+
18
+ The six dimensions are: **grammar**, **nav treatment**, **hero device**,
19
+ **act-sequence shape**, **close pattern**, **signature move**.
20
+
21
+ Dimension 6 is free, because a signature move is unique by definition. So the
22
+ gate really asks for three more out of the remaining five, and a build that
23
+ changes only grammar and world will fail it.
24
+
25
+ ---
26
+
27
+ ## The registry
28
+
29
+ | Build | Grammar | Nav treatment | Hero device | Act-sequence shape | Close pattern | Signature move | World | Port |
30
+ |---|---|---|---|---|---|---|---|---|
31
+
32
+ *(empty: your first build has nothing to clear, so build whatever the interview
33
+ points at. From the second onwards, this table is the constraint.)*
34
+
35
+ ---
36
+
37
+ ## What is taken
38
+
39
+ Add a bullet here whenever a build claims something a later build should avoid
40
+ reusing: a grammar, a nav treatment, a close pattern, a signature move, an
41
+ act-count-and-length band. The shared columns are what the next build inherits
42
+ as a constraint, so writing them down is the whole point.
43
+
44
+ Nothing is taken yet.
45
+
46
+ ---
47
+
48
+ ## Appending a row
49
+
50
+ After shipping, add one line to the table and one bullet to **What is taken** if
51
+ the build claimed something new. Fill every column. Say what the build shares
52
+ with existing rows.
53
+
54
+ Rows are append-only. A build that has been superseded stays in the table,
55
+ because the space it occupies is still occupied.
56
+
57
+ ---
58
+
59
+ ## Worked example
60
+
61
+ The skill's author kept a registry of twelve builds across eight page grammars.
62
+ If you want to see what a filled-in table looks like, and which shapes tend to
63
+ collide, read `EXAMPLES.md` in the luckiest-design-website repository. Treat it as
64
+ illustration only: those rows are somebody else's builds and they do **not**
65
+ constrain yours.