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,349 @@
1
+ # Worldflight: the continuous-world page mode
2
+
3
+ Act mode cuts the page into pinned blocks. That is the right shape for a page of
4
+ chapters and the wrong shape for one unbroken camera move, and building a
5
+ continuous world out of acts produces exactly the page an owner described as
6
+ "awful": you scroll down, the stage unsticks, a static page slides past, clean
7
+ horizontal edges travel up the screen, and then you start scrolling down again.
8
+ Every one of those defects is the same defect. A pinned act is a block in the
9
+ document, and a document made of blocks has seams.
10
+
11
+ Worldflight removes the seams by removing the blocks.
12
+
13
+ There is **one** `position: fixed` stage for the whole page. Every leg of the
14
+ flight is mounted in it at once and stays mounted. The only element in document
15
+ flow is an empty spacer. Scroll drives two things and nothing else: the film
16
+ timeline and the opacity of the overlay. Nothing travels, nothing pins, nothing
17
+ unpins, and there is no boundary anywhere for a seam to show at.
18
+
19
+ ---
20
+
21
+ ## 1. The markup
22
+
23
+ ```html
24
+ <div data-sc-mode="worldflight" data-sc-seam="0.12">
25
+
26
+ <div data-sc-world>
27
+ <div data-sc-segment data-sc-w="0.95" data-sc-linger="0.3"
28
+ data-sc-waypoint="Surface">
29
+ <img class="sc-world__poster" src="assets/p1.webp" alt="" decoding="async">
30
+ <video data-sc-src="assets/leg1.mp4"
31
+ data-sc-src-mobile="assets/leg1-m.mp4"></video>
32
+ </div>
33
+ <div data-sc-segment data-sc-w="0.9" data-sc-linger="0.42"
34
+ data-sc-waypoint="Thermocline">
35
+ <img class="sc-world__poster" src="assets/p2.webp" alt="" decoding="async">
36
+ <video data-sc-src="assets/leg2.mp4"
37
+ data-sc-src-mobile="assets/leg2-m.mp4"></video>
38
+ </div>
39
+ <!-- legs in flight order, as many as the world has -->
40
+ </div>
41
+
42
+ <div data-sc-world-copy>
43
+ <div class="sc-world__scrim sc-scrim sc-scrim--band"></div>
44
+ <div class="sc-copy sc-copy--lead" data-sc-copy data-sc-window="hero"> … </div>
45
+ <div class="sc-copy sc-copy--trail" data-sc-copy data-sc-window="0.38 0.66"> … </div>
46
+ <div class="sc-copy sc-copy--lead" data-sc-copy data-sc-window="finale"> … </div>
47
+ </div>
48
+
49
+ <div data-sc-spacer aria-hidden="true"></div>
50
+ </div>
51
+ ```
52
+
53
+ `ScrollCraft.mount(document)` as usual. The mode composes with nothing else on
54
+ the page: a worldflight page has no acts.
55
+
56
+ ### Attributes
57
+
58
+ | Attribute | On | Default | What it does |
59
+ |---|---|---|---|
60
+ | `data-sc-mode="worldflight"` | mode root | n/a | Turns the page into one flight. |
61
+ | `data-sc-seam` | mode root | `0.12` | Crossfade band, in viewport-heights of scroll. Clamped 0.02 to 0.4. |
62
+ | `data-sc-world` | stage | n/a | The single fixed stage. Gets `.sc-world`. |
63
+ | `data-sc-segment` | leg | n/a | One leg. Holds a poster and a clip. |
64
+ | `data-sc-w` | leg | `1.3` | Scroll this leg owns, in viewport-heights. |
65
+ | `data-sc-linger` | leg | `0` | Dwell remap for this leg only. Clamped to 0.6. |
66
+ | `data-sc-waypoint` | leg | n/a | Label published on the waypoint event. |
67
+ | `data-sc-world-copy` | copy layer | n/a | Fixed overlay. Gets `.sc-world__copy`. |
68
+ | `data-sc-copy` | copy block | n/a | A windowed block of type. |
69
+ | `data-sc-window` | copy block | n/a | `hero` \| `finale` \| `from to [in [out]]`. |
70
+ | `data-sc-spacer` | spacer | n/a | The scroll track. Engine sets its height. |
71
+ | `data-sc-lerp` | root or `<video>` | `0.18` | Playhead smoothing. See devices.md. |
72
+
73
+ The engine generates no DOM here, the same as in act mode. It sets the spacer's
74
+ height, and it writes opacity, visibility and z-index on the legs. Everything
75
+ else is markup you wrote.
76
+
77
+ ---
78
+
79
+ ## 2. The scroll track
80
+
81
+ The spacer's height is **(sum of the leg weights + 1) viewport-heights**, set in
82
+ pixels so it and the stage are measured on the same ruler (the stage is sized in
83
+ `svh`, and on a phone `vh` and `svh` are different numbers).
84
+
85
+ The `+ 1` is not padding. Without it the track ends at the exact scroll position
86
+ where the last leg reaches progress 1, so the final second of the last clip is a
87
+ place the reader can never come to rest. One extra viewport gives the last
88
+ flight room to land.
89
+
90
+ Position along the track, `t`, is measured in viewport-heights, which is the
91
+ same unit the weights are written in. Leg *i* owns `[c_i, c_i + w_i)`, and its
92
+ local progress is `(t - c_i) / w_i`, remapped through `lingerEase`.
93
+
94
+ ---
95
+
96
+ ## 3. The seam
97
+
98
+ Two things make a boundary between clips invisible, and both are required.
99
+
100
+ **The assets have to match at the seam** (section 6). The engine cannot fix a
101
+ mismatched cut.
102
+
103
+ **The crossfade has to be one-sided.** Over the seam band the incoming leg fades
104
+ up from 0 to 1 while the outgoing leg holds at full strength underneath it, and
105
+ the outgoing leg only drops to zero once it is completely covered. Fading both
106
+ sides at once puts the page ground through the middle of every seam, which reads
107
+ as a flash, and it is the obvious implementation. z-index favours the current
108
+ leg (120) over the rest (100 + opacity × 10).
109
+
110
+ Each side of the band is half a seam width, so each leg holds its seam frame for
111
+ about 0.06vh of scroll. Those are exactly the frames the seam law matched, so a
112
+ held frame there is invisible by construction.
113
+
114
+ Nothing ever swaps a `src`. A src swap is a black frame, and a black frame is
115
+ the cut this mode exists to remove.
116
+
117
+ ---
118
+
119
+ ## 4. The copy contract
120
+
121
+ Copy lives in one fixed layer above the stage. Each block declares a window
122
+ against the **whole track**, not against a leg.
123
+
124
+ - `data-sc-window="hero"`: present from the first pixel, fades out by 0.62 of
125
+ the first leg. A hero that fades IN has to fade in over an empty first screen,
126
+ which is the one moment on the page with nothing else to look at.
127
+ - `data-sc-window="finale"`: fades in from 0.4 of the last leg and holds to the
128
+ end.
129
+ - `data-sc-window="0.38 0.66"`: a plateau window across those track fractions:
130
+ ramps in over the first 30%, holds at full opacity, ramps out over the last
131
+ 30%. Add a third and fourth number to set the ramps yourself. The plateau is
132
+ not decoration: a pure triangle touches opacity 1 for one instant, so the
133
+ reader has to stop on exactly the right pixel to see the line at full strength
134
+ and every heading reads slightly faded.
135
+
136
+ **The only transform on the copy side is `translateY`, and it is capped at 4vh
137
+ across the whole window** (from +2vh to -2vh). Anything larger stops reading as
138
+ a layer over a moving world and starts reading as a second page scrolling at a
139
+ different speed, which is the cheapness this mode replaces. Pointer events are
140
+ handed back to a block only above opacity 0.5.
141
+
142
+ `.sc-world__scrim` is provided for a scrim div on the copy side. Shape it to
143
+ where the copy actually sits. The stock `.sc-scrim--band` tops out at 58% of the
144
+ frame, and footage that stays bright past that will fail the contrast pass even
145
+ though the page looks fine.
146
+
147
+ ---
148
+
149
+ ## 5. The route rail
150
+
151
+ The engine publishes the current leg index as `--sc-seg` and its local progress
152
+ as `--sc-segp`, on the mode root and on `:root`, and fires a `sc:waypoint`
153
+ CustomEvent (bubbling, `detail: { index, count, label, el, progress }`) whenever
154
+ the leg changes.
155
+
156
+ It renders no rail. A gauge, a map, a depth readout, a leg counter and a set of
157
+ chapter dots are all the same two numbers, and a runtime that ships one of them
158
+ ships it to every page that uses this mode. Build the rail in the page:
159
+
160
+ ```js
161
+ addEventListener('sc:waypoint', (e) => {
162
+ document.querySelectorAll('.rail__leg').forEach((el) => {
163
+ el.setAttribute('aria-current', String(+el.dataset.leg === e.detail.index));
164
+ });
165
+ });
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 6. The seam law for assets
171
+
172
+ A worldflight is only as good as the joins between its clips. Two architectures
173
+ work; nothing else does.
174
+
175
+ **Architecture A (preferred): chain on start images only.** Each leg is
176
+ generated from a start image and left to end wherever it ends. The next leg's
177
+ start image is a frame pulled from the previous leg's **encoded** mp4. Never
178
+ force an end-image wide shot: an image-to-image model asked to hit both ends
179
+ resolves the conflict by pulling the camera back, and every leg ends up as the
180
+ same wide establishing shot.
181
+
182
+ **Architecture B: connector legs.** Where two existing clips have to meet, cut a
183
+ short connector whose start frame comes from the previous leg and whose end
184
+ frame is the next leg's actual first frame.
185
+
186
+ Extract from the ENCODED mp4, not the source render. The encode changes the
187
+ pixels, and a poster or a chain frame taken from the pre-encode master does not
188
+ match the frame the browser will actually decode:
189
+
190
+ ```bash
191
+ # last frame of the previous leg, as the next leg's start image
192
+ ffmpeg -sseof -0.15 -i legN.mp4 -frames:v 1 -q:v 2 chainN.png
193
+ # first frame of a leg, for its poster
194
+ ffmpeg -i legN.mp4 -frames:v 1 -q:v 3 pN.webp
195
+ ```
196
+
197
+ ### Encoding
198
+
199
+ Same rules as any scrub clip, and they matter more here because a worldflight
200
+ has more of them mounted at once.
201
+
202
+ - **GOP 8 desktop, GOP 4 mobile.** Scrubbing is random access; a long GOP means
203
+ every seek decodes a run of frames and the playhead lags behind the hand.
204
+ - Ship `data-sc-src-mobile` for every leg. The engine picks it on coarse
205
+ pointers and narrow viewports.
206
+ - Posters as WebP, extracted as above.
207
+
208
+ ---
209
+
210
+ ## 7. Loading
211
+
212
+ A leg is fetched only while the reader is within **±1.6vh** of it. Loading the
213
+ whole flight up front is tens of megabytes before the first frame paints;
214
+ loading on arrival means arriving at a poster.
215
+
216
+ Until a leg's first real frame has painted, its poster carries the move with a
217
+ push-in (`scale(1.03 + local × 0.14)`). A still that sits perfectly still while
218
+ the page scrolls announces itself as a placeholder; a slow push reads as the
219
+ camera already flying.
220
+
221
+ Under reduced motion **no clip is ever fetched**. The posters are the film, they
222
+ cross-dissolve through exactly the same seams at exactly the same scroll
223
+ positions, the same copy windows open and close, and every transform is dropped.
224
+ The whole story still reads.
225
+
226
+ ---
227
+
228
+ ## 7b. The spacer is sized once, at mount
229
+
230
+ `layout()` writes the spacer height as `(total + 1) * innerHeight`. If
231
+ `innerHeight` reports 0 at that moment the spacer is set to **0px**, the page
232
+ has no scroll track, and the flight never advances.
233
+
234
+ It fails silently and it looks like success. The engine mounted, every leg
235
+ registered, the clips fetched and decoded, `sc-has-clip` is on the segments, and
236
+ there is nothing in the console. The page is simply a still image that cannot be
237
+ scrolled. Embedded preview panes and some early loads do exactly this.
238
+
239
+ Do not fix it in the engine. One resize makes it re-measure correctly, so send
240
+ one from the page once the window and the fonts have settled:
241
+
242
+ ```js
243
+ function relayout() { dispatchEvent(new Event('resize')); }
244
+ addEventListener('load', relayout);
245
+ if (document.fonts && document.fonts.ready) document.fonts.ready.then(relayout);
246
+ ```
247
+
248
+ The `fonts.ready` half earns its place independently: a webfont swapping in
249
+ changes the measured height of every copy block, and anything the page sized
250
+ against those blocks (a scrim plate, a rail) is wrong until it re-measures.
251
+
252
+ Check it with one line, and check it before blaming anything else:
253
+
254
+ ```js
255
+ document.documentElement.scrollHeight // must be ~(sum of weights + 1) * innerHeight
256
+ ```
257
+
258
+ ## 7c. Pace: one speed, and slower than you think
259
+
260
+ Two separate faults get described as "it doesn't feel smooth", and only one of
261
+ them is smoothing.
262
+
263
+ **Inconsistent pace is the worse one.** Leg weight divided by clip length is how
264
+ fast the world moves under the reader's hand. If that number varies from leg to
265
+ leg, the world surges and drags for no reason the reader can see, and it reads
266
+ as a fault in the page rather than as pacing. Give every leg with the same clip
267
+ length the **same weight**, and give a longer clip a proportional one. On
268
+ `orrery` that number varied by 36% across ten legs on the first cut, and the
269
+ owner's word for it was "not smooth". Evened to a 6% spread, the same footage
270
+ reads as one continuous move.
271
+
272
+ ```
273
+ rate = weight / clip_seconds // hold this within a few percent everywhere
274
+ ```
275
+
276
+ **Then slow it down.** The instinct is to spend as little scroll as possible. A
277
+ fly-through wants the opposite: the reader is steering a camera, and a camera
278
+ that answers too eagerly feels twitchy. **0.21 to 0.22vh per second of film is a
279
+ good floor for a world you fly through.** 0.14 to 0.19, which is what the per-8s
280
+ line yields, is noticeably fast.
281
+
282
+ That line is a **dead-scroll guardrail, not a taste ceiling.** Exceeding it is
283
+ fine and often correct; exceeding it without checking is not. The harness
284
+ defines dead scroll mechanically and will tell you. At 0.216vh/s a 0.12vh sample
285
+ gap still advances the clip by half a second, nowhere near dead.
286
+
287
+ **Damp the playhead and widen the joins.** `data-sc-lerp` defaults to 0.18;
288
+ **0.12 is the better default for a worldflight**, because a flight has more legs
289
+ mounted and more seams than an act page, and the extra damping is what actually
290
+ removes wheel-event judder. Widen `data-sc-seam` from 0.12 to ~0.16 for the same
291
+ reason: a longer crossfade band gives each join more room to disappear in.
292
+
293
+ Changing weights moves every leg boundary, so **every `data-sc-window` has to be
294
+ recomputed** against the new track and then re-checked on screen. A copy window
295
+ is tuned to a frame of film, not to a number.
296
+
297
+ ## 8. Hard rules
298
+
299
+ | Rule | Why |
300
+ |---|---|
301
+ | **Nothing in document flow but the spacer.** | The moment a real block scrolls past the fixed stage, the page has a seam and the mode is pointless. If you want a section, you want act mode. |
302
+ | **Copy translate ≤ 4vh across a window.** | Larger reads as a second page scrolling at a different speed. |
303
+ | **The lerp is never disabled** except under reduced motion. | A 1:1 playhead reproduces every gap in the wheel event stream as a stutter. |
304
+ | **One pace for the whole flight**, and slower than feels necessary. | Weight divided by clip length must match across legs, or the world surges and drags. ~1.5vh per 8s is the dead-scroll guardrail, not the target. See section 7c. |
305
+ | **Every clip stays mounted. Never swap a `src`.** | A src swap is a black frame. |
306
+ | **Seam frames come from the encoded mp4.** | The encode changes the pixels. |
307
+ | **One accent, one scrim shape, copy anchored off the bright centre.** | Verified by the contrast pass, which grades copy blocks exactly like cues, at the worst frame each line is ever shown on. |
308
+
309
+ ---
310
+
311
+ ## 9. Verifying
312
+
313
+ `shoot.mjs` detects `[data-sc-mode="worldflight"]` and switches modes. It samples
314
+ across the spacer track at the same density it samples acts, plus four extra
315
+ positions across every seam, and it waits for the lerp to settle before each
316
+ shot (a screenshot taken mid-lerp is a frame the page never actually holds, and
317
+ it makes the run unrepeatable).
318
+
319
+ It reports:
320
+
321
+ - **dead scroll**, defined here as no leg advancing its `currentTime`, no
322
+ crossfade progress, and no copy-window opacity change between two samples more
323
+ than 0.12vh apart. Skipped under reduced motion, where each leg legitimately
324
+ holds one still frame.
325
+ - **legs that never reach full opacity**: a weight or a seam that is wrong: the
326
+ reader is shown a permanent dissolve and never the leg itself.
327
+ - **legs stuck on poster**: a clip that never loaded or never decoded. It passes
328
+ every other check, because a poster looks exactly like a paused film.
329
+ - **contrast** on visible copy blocks, through the same direction-aware
330
+ compositing path as cues.
331
+
332
+ ```bash
333
+ node scripts/serve.mjs --root builds/<name> --port 45XX
334
+ node scripts/shoot.mjs --url http://localhost:45XX --out lab/<name>-shots --per-act 8
335
+ node scripts/shoot.mjs --url http://localhost:45XX --out lab/<name>-reduced --reduced-motion
336
+ ```
337
+
338
+ The mechanical assertions ship with the skill as
339
+ `scripts/worldflight-assert.mjs` and run against **any** worldflight page, not a
340
+ special rig: spacer height, fixed stage, nothing in document flow, lerp
341
+ convergence and non-overshoot, seam monotonicity, the copy transform cap, and
342
+ the reduced-motion contract.
343
+
344
+ ```bash
345
+ node <skill>/scripts/worldflight-assert.mjs --url http://localhost:45XX
346
+ ```
347
+
348
+ Run it against your own build before the contact sheet. It answers "does the
349
+ mode actually hold" in a way a screenshot cannot.
@@ -0,0 +1,178 @@
1
+ # Worlds
2
+
3
+ The art direction the whole page lives inside. Pick one, write it as a **style
4
+ preamble**, and paste that preamble verbatim at the top of every image and video
5
+ prompt. Reusing it word for word is what makes eight separately generated assets
6
+ look like one shoot. Paraphrasing it is what makes them look like eight prompts.
7
+
8
+ ---
9
+
10
+ ## The default is photographic
11
+
12
+ Soft matte low-poly clay diorama, isometric miniature, tilt-shift toy world:
13
+ **banned as a default.** It is the house style of AI scroll sites, it announces
14
+ that nothing on the page is real, and for any brand selling an actual product it
15
+ actively works against the sale. A clay render of a can does not make anyone
16
+ thirsty.
17
+
18
+ Use an illustrated world only when the brand is genuinely illustrated: a
19
+ children's product, a game, a brand whose existing identity is drawn. Then
20
+ commit to it properly, matched to the brand's real illustration style, not to
21
+ the generic diorama look.
22
+
23
+ ---
24
+
25
+ ## The eight
26
+
27
+ Each is a starting preamble. Tune the light, lens and grade to the brand; keep
28
+ the structure.
29
+
30
+ ### 1. Low-key cinematic: the default
31
+ Dark, controlled, one light source. Works for almost anything premium: spirits,
32
+ coffee, tools, apparel, software, professional services.
33
+
34
+ > Cinematic product photography shot on 35mm anamorphic lenses. Shallow depth of
35
+ > field, high dynamic range, true blacks, matte film grain. Low-key lighting:
36
+ > one warm key, cool ambient fill, deep falloff into shadow. Colour grade of deep
37
+ > charcoal, warm amber highlights, desaturated mid-tones. Photographic realism.
38
+ > NOT 3D render, NOT clay, NOT illustration, NOT CGI, no digital glow, no plastic
39
+ > sheen.
40
+
41
+ ### 2. High-key editorial
42
+ Bright, airy, shadowless. Wellness, skincare, home goods, healthcare, fintech
43
+ that wants to feel calm.
44
+
45
+ > Editorial still-life photography on a seamless bone-white cyclorama. Large soft
46
+ > overhead source, huge white bounce, near-shadowless with one soft contact
47
+ > shadow. High key, gentle contrast, colour grade of warm white and pale
48
+ > neutrals. Medium-format sharpness, fine grain. Photographic realism, no CGI.
49
+
50
+ ### 3. Natural documentary
51
+ Real people, real places, available light. Service businesses, trades,
52
+ restaurants, anything where trust comes from "these are actual humans."
53
+
54
+ > Documentary photography, available light only, handheld 35mm. Natural skin
55
+ > tones, honest imperfect surfaces, slight motion in the frame. Muted realistic
56
+ > grade, no colour cast, visible grain. Candid, unposed, nobody looking at
57
+ > camera. Absolutely not stock-photo styling, no fake smiles, no CGI.
58
+
59
+ ### 4. Hard-light graphic
60
+ Direct sun, saturated ground, sharp shadows as composition. Streetwear, energy
61
+ drinks, sports, youth brands.
62
+
63
+ > Studio product photography with a single hard undiffused source. Crisp
64
+ > high-contrast shadows used as graphic shapes. Saturated seamless colour
65
+ > backdrop. Punchy contrast, slight halation on specular highlights. Shot on
66
+ > digital medium format, sharp throughout. Photographic, not rendered.
67
+
68
+ ### 5. Macro texture
69
+ Closer than the eye gets. Food, drink, materials, ingredients, craft.
70
+
71
+ > Extreme macro photography, 100mm macro lens at high magnification. Razor-thin
72
+ > plane of focus, everything else falling to soft black. Backlit so edges glow.
73
+ > Visible surface texture, condensation, grain of the material. Near-black
74
+ > negative space. Photographic realism, no CGI, no illustration.
75
+
76
+ ### 6. Architectural
77
+ Space, scale, geometry, almost no people. Real estate, agencies, manufacturing,
78
+ B2B infrastructure.
79
+
80
+ > Architectural photography, tilt-shift corrected verticals, wide 24mm. Vast
81
+ > negative space, strong linear geometry, raking daylight through structure.
82
+ > Cool neutral grade with one warm accent. Long exposure stillness. Photographic,
83
+ > no render, no CGI.
84
+
85
+ ### 7. Nocturne
86
+ Night, practical lights, wet surfaces, reflection. Nightlife, automotive,
87
+ gaming, security, anything with edge.
88
+
89
+ > Night photography, practical light sources only: neon, sodium, screen glow.
90
+ > Wet reflective surfaces doubling every light. Deep blue-black shadows, warm
91
+ > point highlights, heavy atmosphere. Anamorphic flare, visible grain.
92
+ > Photographic, cinematic, not rendered.
93
+
94
+ ### 8. Technical drawing
95
+ The one non-photographic world that reads as premium rather than cheap, because
96
+ it is honest about being a diagram. Engineering, hardware, complex services.
97
+
98
+ > Precise technical illustration in the style of a patent drawing or exploded
99
+ > assembly diagram. Fine consistent line weight, no fills, monochrome ink on
100
+ > warm paper ground, dimension lines and leader callouts. Orthographic
101
+ > projection. Restrained, engineered, no shading, no gradients, no 3D render.
102
+
103
+ ---
104
+
105
+ ## Writing your own
106
+
107
+ Every preamble names five things. Miss one and the set drifts.
108
+
109
+ 1. **Medium and lens**: "35mm anamorphic", "100mm macro", "handheld 35mm".
110
+ This is what sets depth of field and perspective.
111
+ 2. **Light**: count the sources and place them. "One warm key, cool ambient
112
+ fill" is directable. "Beautiful lighting" is not.
113
+ 3. **Grade**: the colour story in three words. "Deep charcoal, warm amber,
114
+ desaturated mid-tones."
115
+ 4. **Texture**: grain, halation, condensation, imperfection. This is what makes
116
+ an image read as photographed rather than generated. Skipping it is the
117
+ single biggest cause of the plastic AI look.
118
+ 5. **The negative list**: what it must not be. "NOT 3D render, NOT clay, NOT
119
+ illustration, no digital glow, no plastic sheen." Models drift toward
120
+ rendered-looking output; the negative list is what holds them.
121
+
122
+ ---
123
+
124
+ ## If the canvas is light
125
+
126
+ Every worked example above is dark, and a high-key build inverts four things at
127
+ once. Getting them wrong costs a full iteration:
128
+
129
+ 1. **The scrim washes toward the canvas, it does not darken.** Over footage on a
130
+ paper ground the density has to go *up*, not down, or the type has nothing to
131
+ sit on.
132
+ 2. **The type over media stays ink.** Light text on a light page over a bright
133
+ frame is unrecoverable.
134
+ 3. **`--sc-edge` is an ink-tinted inset highlight** tuned for a near-black
135
+ ground. On paper it reads as dirt along the top lip. Invert it to white, or
136
+ drop it and carry the raise with a hairline.
137
+ 4. **The `--sc-e1/2/3` shadow alphas are tuned for a near-black ground** too.
138
+ Halve them and re-tint `--sc-shadow-color` toward the canvas hue.
139
+
140
+ The contrast direction flips with all of this: dark type fails on the *darkest*
141
+ patch under it, not the brightest. The harness picks the direction per line, so
142
+ it grades a high-key page correctly (see [verify.md](verify.md)).
143
+
144
+ ---
145
+
146
+ ## Composition, per shot
147
+
148
+ The preamble sets the world. Each shot prompt then names the subject, the frame,
149
+ and **where the empty space is**.
150
+
151
+ **Name the empty space in every scene prompt, not only in the preamble.** This
152
+ is a requirement, not advice. It is the single highest-leverage line in a prompt:
153
+ seven stills across three aspect ratios came back on-grade and cohesive with zero
154
+ rerolls on the build that did it every time. Copy sits on these images, so
155
+ composition has to leave room for it:
156
+
157
+ - "large empty shadowed space across the upper left of the frame"
158
+ - "the subject low and to the right, negative space above"
159
+ - "centred with even empty space on both sides"
160
+
161
+ Generate the space, do not crop for it later. And never ask for text in the
162
+ image: markup is selectable, translatable, sharp at every density, and editable
163
+ after the fact.
164
+
165
+ ---
166
+
167
+ ## Cohesion checks
168
+
169
+ Lay every generated asset side by side and look for:
170
+
171
+ - **One light direction** across the set, or a deliberate reason it changes.
172
+ - **One grade.** If one image is cooler than the rest, reroll it rather than
173
+ correcting it in CSS; a filter over a full-bleed image flattens it.
174
+ - **One level of realism.** A photoreal hero followed by a rendered-looking
175
+ product shot is worse than either style used consistently.
176
+ - **The brand object identical everywhere.** Pass the real packaging, logo or
177
+ product shot as `--ref` on every prompt that includes it. A label that drifts
178
+ between shots is the thing a client notices first.