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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/luckiest-design-website/ATTRIBUTION.md +38 -0
- package/skills/luckiest-design-website/CHANGELOG.md +104 -0
- package/skills/luckiest-design-website/LICENSE +21 -0
- package/skills/luckiest-design-website/SKILL.md +372 -0
- package/skills/luckiest-design-website/UPSTREAM-CHANGELOG.md +526 -0
- package/skills/luckiest-design-website/engine/scrollcraft.css +432 -0
- package/skills/luckiest-design-website/engine/scrollcraft.js +1167 -0
- package/skills/luckiest-design-website/evals/evals.json +33 -0
- package/skills/luckiest-design-website/references/assets.md +286 -0
- package/skills/luckiest-design-website/references/device-diag.html +214 -0
- package/skills/luckiest-design-website/references/devices.md +466 -0
- package/skills/luckiest-design-website/references/feel.md +277 -0
- package/skills/luckiest-design-website/references/registry-examples.md +305 -0
- package/skills/luckiest-design-website/references/taste.md +304 -0
- package/skills/luckiest-design-website/references/template.html +138 -0
- package/skills/luckiest-design-website/references/uniqueness.md +480 -0
- package/skills/luckiest-design-website/references/verify.md +381 -0
- package/skills/luckiest-design-website/references/worldflight.md +349 -0
- package/skills/luckiest-design-website/references/worlds.md +178 -0
- package/skills/luckiest-design-website/scripts/doctor.mjs +177 -0
- package/skills/luckiest-design-website/scripts/encode.sh +80 -0
- package/skills/luckiest-design-website/scripts/kie.mjs +202 -0
- package/skills/luckiest-design-website/scripts/serve.mjs +52 -0
- package/skills/luckiest-design-website/scripts/shoot.mjs +644 -0
- package/skills/luckiest-design-website/scripts/workspace.mjs +106 -0
- package/skills/luckiest-design-website/scripts/worldflight-assert.mjs +273 -0
- 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.
|