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,526 @@
|
|
|
1
|
+
# scrollcraft changelog
|
|
2
|
+
|
|
3
|
+
Dated notes on what changed in the skill and which build's finding drove it.
|
|
4
|
+
Builds live in `OtherWorlds/Ultimate Websites/builds/`; each carries a
|
|
5
|
+
`BUILD-REPORT.md`.
|
|
6
|
+
|
|
7
|
+
## 2026-08-23: iOS clip priming hardened; real-device diagnostic added
|
|
8
|
+
|
|
9
|
+
Driven by a build whose hero clip sat frozen on the owner's actual iPhone
|
|
10
|
+
through four rounds of fixes while every headless probe reported it scrubbing.
|
|
11
|
+
The working clip further down the same page was the clue; the only difference
|
|
12
|
+
between them was being first.
|
|
13
|
+
|
|
14
|
+
**Engine, `engine/scrollcraft.js`**
|
|
15
|
+
|
|
16
|
+
- Each clip is primed at `loadedmetadata`, not only on a later gesture. A
|
|
17
|
+
muted inline `play()` needs no activation outside Low Power Mode; where it
|
|
18
|
+
is rejected, gesture listeners retry per clip until every clip is primed.
|
|
19
|
+
(The previous one-shot first-touch prime lost a race: the reader touches to
|
|
20
|
+
scroll while the hero is still downloading, and the shot was spent on a
|
|
21
|
+
sourceless element.)
|
|
22
|
+
- `touchend` and `click` joined the prime listeners. The HTML spec's
|
|
23
|
+
activation-triggering events include `touchend` but not `touchstart`, so a
|
|
24
|
+
Low Power Mode phone gets a valid attempt when the finger lifts.
|
|
25
|
+
- The priming flag releases on a 2s timer. iOS may leave a `play()` promise
|
|
26
|
+
pending forever, which jammed the retry permanently.
|
|
27
|
+
- A seek stuck past 700ms is re-issued. `tick()` skips a clip while
|
|
28
|
+
`el.seeking` is true, so one hung seek froze that clip for the life of the
|
|
29
|
+
page through the guard meant to protect it.
|
|
30
|
+
- The poster reveal fires on a 2.5s timeout as well as on `seeked`, so a clip
|
|
31
|
+
whose `seeked` never arrives cannot stay invisible forever.
|
|
32
|
+
- `muted` and `playsInline` are set as properties at load time, not only as
|
|
33
|
+
attributes; iOS treats the two differently.
|
|
34
|
+
|
|
35
|
+
**Docs and references**
|
|
36
|
+
|
|
37
|
+
- `references/verify.md`: new sections "The phone is a different machine"
|
|
38
|
+
(what headless cannot see, what the engine now handles on iOS), "Ship the
|
|
39
|
+
diagnostic with the site", and "Ask what differs before asking what's
|
|
40
|
+
broken", plus three failure-table rows. Mobile named a first-class target.
|
|
41
|
+
- `references/device-diag.html`: standalone real-device scrub diagnostic.
|
|
42
|
+
Suspect clip loaded two ways beside a known-good clip, MOVING / FROZEN
|
|
43
|
+
verdict per pane, prime results and distinct-frame counts on screen. Edit
|
|
44
|
+
its TESTS array and deploy beside the site on the first mobile report.
|
|
45
|
+
- `SKILL.md` Step 5 now states the real-device gap explicitly.
|
|
46
|
+
|
|
47
|
+
## 2026-08-22: bespoke fixed-stage verification state
|
|
48
|
+
|
|
49
|
+
Driven by the owner's cold-scroll finding on PHASE: "Nothing happens when I
|
|
50
|
+
start scrolling. Like literally nothing's happening in the first couple of
|
|
51
|
+
scenes." The first pass had passed mechanically because the experience used
|
|
52
|
+
`flow` sections as invisible scroll markers around a page-local fixed stage.
|
|
53
|
+
Ordinary flow is deliberately excluded from dead-scroll detection, so the
|
|
54
|
+
harness never inspected the stage's visible timeline.
|
|
55
|
+
|
|
56
|
+
**Harness: `scripts/shoot.mjs`**
|
|
57
|
+
|
|
58
|
+
- Reads optional `data-sc-verify-state` signatures from bespoke fixed stages
|
|
59
|
+
and includes them in the dead-scroll signature.
|
|
60
|
+
- Flow spans carrying a custom state are now checked like pinned spans.
|
|
61
|
+
- Reads `data-sc-verify-hold="true"` as an explicit authored hold, including
|
|
62
|
+
resolved closes and deliberately stable reduced-motion frames.
|
|
63
|
+
- The contract requires rendered values, not raw scroll progress, so a page
|
|
64
|
+
cannot make a static composition pass by publishing a changing percentage.
|
|
65
|
+
|
|
66
|
+
**Docs**
|
|
67
|
+
|
|
68
|
+
- `references/verify.md` documents the custom-state contract, the authored-hold
|
|
69
|
+
escape hatch, the reduced-motion rule, and the failure mode.
|
|
70
|
+
|
|
71
|
+
**PHASE correction**
|
|
72
|
+
|
|
73
|
+
- Opening act now moves the split, product scale and position, headline, and
|
|
74
|
+
metadata from the first wheel gestures.
|
|
75
|
+
- The frustration act now transfers dominance from the burn state to the
|
|
76
|
+
forgotten state while the seam crosses the cup.
|
|
77
|
+
- Act progress no longer clamps early; the close reaches a true 0% divider.
|
|
78
|
+
- Dense six-samples-per-act sheets replaced the earlier coarse pass.
|
|
79
|
+
|
|
80
|
+
## 2026-08-21: worldflight mode and the global smoothed playhead
|
|
81
|
+
|
|
82
|
+
Driven by the owner's verdict on the act-based continuous world: "awful... you're
|
|
83
|
+
literally going from scrolling down to static page and then you start scrolling
|
|
84
|
+
down again... weird clear page lines scrolling up... very cheap looking."
|
|
85
|
+
Mechanics ported from oso95/scroll-world.
|
|
86
|
+
|
|
87
|
+
**Engine: `engine/scrollcraft.js`**
|
|
88
|
+
|
|
89
|
+
- New page mode `data-sc-mode="worldflight"`: one `position: fixed` stage, an
|
|
90
|
+
empty spacer as the only element in document flow, legs mounted for the life
|
|
91
|
+
of the page, and scroll driving only the film timeline and overlay opacity.
|
|
92
|
+
- Spacer height is (sum of `data-sc-w` + 1) viewport-heights, set in pixels so
|
|
93
|
+
the track and the `svh`-sized stage share a ruler.
|
|
94
|
+
- One-sided crossfade over a `data-sc-seam` band (default 0.12vh): the incoming
|
|
95
|
+
leg fades up while the outgoing one holds opaque underneath, so the page
|
|
96
|
+
ground never shows through a seam. z-index 120 for the current leg, 100 +
|
|
97
|
+
opacity × 10 for the rest. No `src` swapping, ever.
|
|
98
|
+
- Per-leg `data-sc-linger`, a monotone dwell remap capped at 0.6 with fixed
|
|
99
|
+
endpoints, so seam frames are never touched.
|
|
100
|
+
- Copy windows against the whole track: `hero`, `finale`, or a `from to` pair
|
|
101
|
+
with a plateau. Only transform is `translateY`, capped at 4vh across a window.
|
|
102
|
+
Pointer events gated above opacity 0.5.
|
|
103
|
+
- Legs prefetch within ±1.6vh; until a real frame paints, the poster carries a
|
|
104
|
+
push-in (`scale(1.03 + local × 0.14)`).
|
|
105
|
+
- Waypoints published as `--sc-seg` / `--sc-segp` plus a bubbling `sc:waypoint`
|
|
106
|
+
event. The engine renders no rail UI.
|
|
107
|
+
- Reduced motion: no clip is ever fetched, posters cross-dissolve through the
|
|
108
|
+
same seams and windows, all transforms dropped.
|
|
109
|
+
- **Playhead retrofit, all scrub clips everywhere.** Every clip now lives in one
|
|
110
|
+
`playheads` list walked by one rAF loop: lerp 0.18 per frame (`data-sc-lerp`,
|
|
111
|
+
clamped 0.02 to 1, 1.0 under reduced motion), 8ms/20ms deadband, seek coalescing,
|
|
112
|
+
offscreen clips skipped within 0.002. Replaces the act-only 0.16 loop. Act
|
|
113
|
+
behaviour is otherwise untouched.
|
|
114
|
+
- iOS priming: one muted play-pause across every clip on the first touch or
|
|
115
|
+
pointer down, so a seeked-but-never-played clip is not left blank.
|
|
116
|
+
- `ScrollCraft.instances` collects mount handles, so the harness can ask whether
|
|
117
|
+
the playhead has arrived instead of guessing with a timeout.
|
|
118
|
+
- Warns when a worldflight stage does not compute `position: fixed`, the same
|
|
119
|
+
silent failure as an unpinned act one level worse.
|
|
120
|
+
- Synced byte-identical to all eight build folders and `cmp`-verified.
|
|
121
|
+
|
|
122
|
+
**Engine: `engine/scrollcraft.css`**
|
|
123
|
+
|
|
124
|
+
- `.sc-world`, `.sc-world__seg`, `.sc-world__poster`, `.sc-world__copy`,
|
|
125
|
+
`.sc-world__scrim`, `.sc-world__spacer`, and `[data-sc-copy]` pre-paint state.
|
|
126
|
+
- `video[data-sc-scrub].sc-has-clip` lights a leg's clip, since a worldflight
|
|
127
|
+
clip has no ancestor act to light it through.
|
|
128
|
+
- Reduced motion drops the poster push-in and the copy drift.
|
|
129
|
+
|
|
130
|
+
**Harness: `scripts/shoot.mjs`**
|
|
131
|
+
|
|
132
|
+
- Detects `[data-sc-mode="worldflight"]` and switches sampling to the track:
|
|
133
|
+
per-leg fractions at the same density as per-act, plus four positions across
|
|
134
|
+
every seam.
|
|
135
|
+
- `settle()` now waits for the playhead to ARRIVE (|cur - target| < 0.002 and not
|
|
136
|
+
seeking) via `ScrollCraft.instances`, falling back to the old currentTime-goes-
|
|
137
|
+
quiet method. Without this every worldflight frame is shot mid-lerp and no run
|
|
138
|
+
is repeatable.
|
|
139
|
+
- Dead scroll for worldflight is no leg advancing `currentTime`, no crossfade
|
|
140
|
+
progress, and no copy-window opacity change across a gap of 0.12vh. Skipped
|
|
141
|
+
under reduced motion, where each leg holds one still frame by design.
|
|
142
|
+
- New findings: legs that never reach full opacity, and legs stuck on poster
|
|
143
|
+
(suppressed under reduced motion).
|
|
144
|
+
- Cue and contrast passes now cover `[data-sc-copy]` alongside `[data-sc-cue]`.
|
|
145
|
+
- The fixed-chrome hide before the contrast shot skips anything inside
|
|
146
|
+
`[data-sc-world]` or `[data-sc-world-copy]`: that stage IS the background
|
|
147
|
+
behind every line and that layer carries the scrim, so hiding them graded the
|
|
148
|
+
copy against the page ground and failed a page that looks fine.
|
|
149
|
+
|
|
150
|
+
**Docs**
|
|
151
|
+
|
|
152
|
+
- New `references/worldflight.md`: the mode, its attributes, the track maths,
|
|
153
|
+
the one-sided seam, the copy-window contract, the waypoint contract, the seam
|
|
154
|
+
law for assets (chain on start images only; connector frames via
|
|
155
|
+
`ffmpeg -sseof -0.15` from the ENCODED mp4), GOP 8 / GOP 4, the ±1.6vh
|
|
156
|
+
prefetch window, and a hard-rules table.
|
|
157
|
+
- `references/uniqueness.md` §2.4: the continuous-world grammar now REQUIRES
|
|
158
|
+
worldflight mode, and says why, quoting the verdict on the act-based attempt.
|
|
159
|
+
- `references/devices.md` §1: new "The playhead is lerped" section covering the
|
|
160
|
+
0.18 rate, the deadband, seek coalescing and `data-sc-lerp`.
|
|
161
|
+
|
|
162
|
+
**Verification**
|
|
163
|
+
|
|
164
|
+
- New rig at `OtherWorlds/Ultimate Websites/lab/worldflight-rig/` (3 descent legs,
|
|
165
|
+
hero/mid/finale copy, page-drawn route rail) on port 4520, with
|
|
166
|
+
`lab/worldflight-assert.mjs`: 24/24 assertions pass.
|
|
167
|
+
- `shoot.mjs` green on the rig desktop and reduced motion; no dead scroll, all
|
|
168
|
+
three legs reach full opacity and paint a real frame, all copy clears 4.5:1.
|
|
169
|
+
- Regression on four act builds (perkform 4500, nateherk 4501, vesper-v2 4504,
|
|
170
|
+
maison 4507): all green, frame counts and poster counts identical to the
|
|
171
|
+
pre-change baselines, zero console errors, zero weak cues.
|
|
172
|
+
|
|
173
|
+
## 2026-08-21: consolidation after the showcase trio (descent, airfield, maison)
|
|
174
|
+
|
|
175
|
+
**Engine: `engine/scrollcraft.js`**
|
|
176
|
+
|
|
177
|
+
- `focusin` now centres a focused control that sits inside a `[data-sc-act]` when
|
|
178
|
+
its own cue computes under 0.85, with `behavior: 'instant'` because
|
|
179
|
+
`scrollcraft.css` sets `scroll-behavior: smooth` and the default animated a
|
|
180
|
+
multi-screen glide with focus off screen throughout. Promoted from airfield's
|
|
181
|
+
page-local handler and guarded to the act stack. Verified: fixes the `flow`
|
|
182
|
+
case; does **not** fix a pinned act, where the sticky stage means centring
|
|
183
|
+
scrolls backwards out of the act and leaves the cue dark. That residue is
|
|
184
|
+
documented in `verify.md` rather than fixed, because the correct scroll target
|
|
185
|
+
needs an inverse of `dwell()`. (airfield, with the limit measured on descent's
|
|
186
|
+
reported case)
|
|
187
|
+
- Synced byte-identical to all eight build folders.
|
|
188
|
+
|
|
189
|
+
**Harness: `scripts/shoot.mjs`**
|
|
190
|
+
|
|
191
|
+
- Null guard on the clip's owning act: `(v.closest("[data-sc-act]") ?? v)`. A
|
|
192
|
+
`video[data-sc-scrub]` outside the act stack, which is the legitimate shape for
|
|
193
|
+
a continuous world, threw a `TypeError` and took the whole run down before it
|
|
194
|
+
wrote anything. Confirmed the old expression throws and the new one returns a
|
|
195
|
+
boolean. (descent)
|
|
196
|
+
|
|
197
|
+
**Encoding: `scripts/encode.sh`**
|
|
198
|
+
|
|
199
|
+
- CRF is now overridable: fourth positional argument, or `SCROLLCRAFT_CRF`,
|
|
200
|
+
positional winning. Defaults unchanged at 20 desktop / 24 mobile, and the
|
|
201
|
+
echoed summary reports the value used. `assets.md` recommends 22-23 for
|
|
202
|
+
grain-heavy worlds, which previously meant not using the script. (descent)
|
|
203
|
+
|
|
204
|
+
**Docs: `references/devices.md`**
|
|
205
|
+
|
|
206
|
+
- §2 `pin`: minimum useful span is ~1.2. At span ≤ 1 a pinned act has one pixel
|
|
207
|
+
of travel, so progress jumps 0 to 1 and every cue and reveal inside it snaps.
|
|
208
|
+
(maison)
|
|
209
|
+
- §4 `reveal`: `clip-path` is relative to the border box, so a reveal on type
|
|
210
|
+
with `line-height` below 1 eats the ascenders and descenders and renders a
|
|
211
|
+
figure as a plain bar. Give it room or wrap it. (maison)
|
|
212
|
+
- §10 `drift`: documented the scoping rule. Drift belongs to the first act whose
|
|
213
|
+
progress is strictly between 0 and 1, so on a page of short acts several
|
|
214
|
+
qualify at once and the ground lags a full section. Paint grounds per section
|
|
215
|
+
instead, which is what a cutlist or chaptered page wants anyway. (airfield,
|
|
216
|
+
with maison's §2.2 conflict resolved the same way)
|
|
217
|
+
|
|
218
|
+
**Docs: `references/taste.md`**
|
|
219
|
+
|
|
220
|
+
- Colour: redefining `--sc-ink` on a subtree does not re-ink text whose `color`
|
|
221
|
+
already computed on `<body>`. Restate `color` on the subtree. (airfield)
|
|
222
|
+
- Colour: the "lock the accent" rule gains its exception. A page that hard-cuts
|
|
223
|
+
between light and dark grounds may carry a two-stop accent, one hue at two
|
|
224
|
+
lightnesses keyed to the ground. Still one accent per ground. (maison)
|
|
225
|
+
- Text over media: `width`/`height` HTML attributes are presentational hints and
|
|
226
|
+
come in pairs. Overriding only one in CSS lets the other resolve to the
|
|
227
|
+
attribute's pixel value. The reference template ships both, so this is a trap
|
|
228
|
+
the template hands to every build. (maison)
|
|
229
|
+
|
|
230
|
+
**Docs: `references/uniqueness.md`**
|
|
231
|
+
|
|
232
|
+
- §2.8 rhythmic cutlist: named the peak collision. The grammar bans `pin` and
|
|
233
|
+
`dwell` while feel.md demands the peak hold. Resolution is to hold in the fixed
|
|
234
|
+
chrome layer and keep every act short and unpinned, generalised to "move the
|
|
235
|
+
peak out of the act stack rather than breaking the grammar". (airfield)
|
|
236
|
+
|
|
237
|
+
**Docs: `references/verify.md`**
|
|
238
|
+
|
|
239
|
+
- Cue fade-outs should land between harness sample positions, or the sheet shows
|
|
240
|
+
half-faded type that nothing grades. Fix `rampOut`, not the sampling. (descent)
|
|
241
|
+
- Keyboard section rewritten to say exactly what the engine's `focusin` handler
|
|
242
|
+
covers and what it does not, with the pinned-act measurement.
|
|
243
|
+
- Six new rows in the failure table for the findings above.
|
|
244
|
+
- Credit accounting: per-call sums overstate real spend, same reason the probe
|
|
245
|
+
delta overstates in the other direction.
|
|
246
|
+
|
|
247
|
+
**Docs: `references/assets.md`**
|
|
248
|
+
|
|
249
|
+
- seedream's aspect ratios: `16:9`, `9:16` and `3:4` verified working, `4:5`
|
|
250
|
+
rejected with an error that does not list the alternatives. Returned pixel
|
|
251
|
+
dimensions are near the ratio, not exact. (maison)
|
|
252
|
+
- Unit costs reframed as published planning rates. Two ledger reconciliations put
|
|
253
|
+
actual debits at roughly 0.4x the per-call sum (1447 vs 530, 2252 vs 856), so
|
|
254
|
+
every budget cap in the skill is conservative and a per-call sum must not be
|
|
255
|
+
reported as measured spend. (orchestrator's ledger reconciliation)
|
|
256
|
+
|
|
257
|
+
**Verification**
|
|
258
|
+
|
|
259
|
+
Desktop and reduced-motion harness passes re-run on descent (4505), airfield
|
|
260
|
+
(4506) and maison (4507) after the engine sync. All six green: no dead scroll,
|
|
261
|
+
all cues clear 4.5:1 at their worst frame, no console errors, no failed
|
|
262
|
+
requests, and every clip reports `poster` under reduced motion.
|
|
263
|
+
|
|
264
|
+
## 2026-08-21: triage of three parallel builds (nateherk, agency, saas)
|
|
265
|
+
|
|
266
|
+
**Engine: `engine/scrollcraft.js`**
|
|
267
|
+
|
|
268
|
+
- `data-sc-count` strips thousands separators before parsing, so a target can be
|
|
269
|
+
written the way it should render (`"0 3,500"`). Previously any real figure
|
|
270
|
+
between 1,000 and 9,999 was unrenderable. (nateherk, merged from its local patch)
|
|
271
|
+
|
|
272
|
+
**Engine: `engine/scrollcraft.css`**
|
|
273
|
+
|
|
274
|
+
- Reduced motion no longer zeroes a `pan` rail into missing content. The stage
|
|
275
|
+
becomes a native `overflow-x: auto` scroll region with proximity snapping, so
|
|
276
|
+
every item stays reachable without motion. (agency and nateherk both hit it;
|
|
277
|
+
perkform and saas both had the hole)
|
|
278
|
+
- `.sc-scrim--trail` switches to a bottom band below 860px, where `.sc-copy--trail`
|
|
279
|
+
re-anchors left and spans the full width. It was darkening the corner the copy
|
|
280
|
+
had just left, which guaranteed a mobile contrast failure over any bright clip.
|
|
281
|
+
(saas)
|
|
282
|
+
- New `.sc-scrim--band` utility: a bottom band, transparent above 58%, for copy
|
|
283
|
+
that spans the frame. (saas)
|
|
284
|
+
|
|
285
|
+
**Harness: `scripts/shoot.mjs`**
|
|
286
|
+
|
|
287
|
+
- Cue opacity reads a kinetic heading through its `.sc-split__i` line units. The
|
|
288
|
+
engine forces the element to 1, so kinetic headlines were reported as peaked on
|
|
289
|
+
frames where every line was at 0. (agency)
|
|
290
|
+
- Contrast is direction-aware: the ink is compared to the mean background and
|
|
291
|
+
graded against the darkest patch when it is dark type, the brightest when it is
|
|
292
|
+
light type. A high-key page was being graded against its most lenient possible
|
|
293
|
+
reading. Ported from the working checker nateherk shipped at
|
|
294
|
+
`lab/min-contrast.mjs`, along with its two corrections: the sampled rect is
|
|
295
|
+
clamped to the viewport, and `position: fixed` chrome is hidden with the text
|
|
296
|
+
because it paints in front of what scrolls under it. (nateherk)
|
|
297
|
+
|
|
298
|
+
**Docs**
|
|
299
|
+
|
|
300
|
+
- `devices.md` §2: only the last act may use a one-value hold cue; a middle act's
|
|
301
|
+
hold stays lit through the un-pin slide and crosses the header. (saas) Plus the
|
|
302
|
+
"ground or greet" rule for any pinned act (agency) and the `"0 1 0 0"` greet-and-hold
|
|
303
|
+
form (agency).
|
|
304
|
+
- `devices.md` §3: reduced-motion behaviour of `pan`, and the rail's copy
|
|
305
|
+
constraint (headings are read cropped). (agency, saas)
|
|
306
|
+
- `devices.md` §6: `data-sc-parallax` documented in its real unit, hundreds of
|
|
307
|
+
pixels rather than viewport fractions, with usable ranges. (agency)
|
|
308
|
+
- `devices.md` §7: `count` is unavailable to concept, fictional and pre-launch
|
|
309
|
+
brands, because every number would be invented. (saas)
|
|
310
|
+
- `devices.md` §8: a flow section after a pinned act takes reduced padding. (saas)
|
|
311
|
+
- `devices.md` §9 and `references/template.html`: `magnet`, `parallax` and `cue`
|
|
312
|
+
all write `transform` and cannot share an element; `data-sc-rise="0"` is the
|
|
313
|
+
guard. The template taught the collision by example. (nateherk)
|
|
314
|
+
- `assets.md`: a section on real supplied footage (measure with `signalstats`,
|
|
315
|
+
expand levels in the pre-encode intermediate, trim before anyone walks into
|
|
316
|
+
frame, target 24-30fps, never stream-copy). (nateherk)
|
|
317
|
+
- `assets.md`: portrait `<picture>` poster-swap pattern and the hand-written
|
|
318
|
+
ffmpeg portrait/crop recipe, since `encode.sh` has no portrait mode. (agency)
|
|
319
|
+
- `assets.md`: grain-heavy worlds run about double the size estimate; measured
|
|
320
|
+
kie.ai unit costs (seedream 5-pro still 28, kling v2-1-pro 5s 160). (agency, saas)
|
|
321
|
+
- `worlds.md`: what inverts when the canvas is light (scrim direction, ink over
|
|
322
|
+
media, `--sc-edge`, shadow alphas). (nateherk) Naming the empty space is now
|
|
323
|
+
stated as a per-shot requirement. (saas)
|
|
324
|
+
- `taste.md`: the positive case behind "no full-frame overlay" (corner, band,
|
|
325
|
+
column, and masking the image away from the text), and stepping the hero
|
|
326
|
+
display size down below ~700px. (agency, nateherk, saas)
|
|
327
|
+
- `verify.md`: how the contrast pass now works, its four known limitations,
|
|
328
|
+
checking that reduced motion did not delete content, and the fact that probe
|
|
329
|
+
deltas cannot attribute credit spend when builds run in parallel.
|
|
330
|
+
|
|
331
|
+
**Deferred, deliberately**
|
|
332
|
+
|
|
333
|
+
- `kie.mjs probe --since <baseline>` and per-call cost logging. Documented in
|
|
334
|
+
`assets.md` and `verify.md` instead.
|
|
335
|
+
- A `portrait` mode for `encode.sh`. The ffmpeg recipe is in `assets.md`.
|
|
336
|
+
- Keying harness cue rows by act and element rather than by text, and the 0.85
|
|
337
|
+
opacity gate on the contrast pass. Both are real, neither blocked a build;
|
|
338
|
+
written down in `verify.md` as known limitations.
|
|
339
|
+
- Lowering the `--sc-t-4xl` floor. It is a token every build inherits, and the
|
|
340
|
+
fix belongs in a page's own phone media query.
|
|
341
|
+
|
|
342
|
+
## 2026-08-21, later the same day
|
|
343
|
+
|
|
344
|
+
- Reduced motion now settles `data-sc-reveal` wipes (`clip-path: none`), the
|
|
345
|
+
same floor the block already applied to parallax, pan and kinetic type. Found
|
|
346
|
+
by the nateherk build; re-verified green on the three reveal-using builds
|
|
347
|
+
(agency, nateherk, vesper-v2).
|
|
348
|
+
- `references/uniqueness.md` added: the structure axis. Eight page grammars, a
|
|
349
|
+
mandatory Step 0 interview, a required per-build signature move, and the
|
|
350
|
+
fingerprint gate against `Ultimate Websites/FINGERPRINTS.md`. Driven by the
|
|
351
|
+
owner's verdict that four builds shared one skeleton.
|
|
352
|
+
- Live-surface honesty rule: the labelled-sample-data escape is spelled out
|
|
353
|
+
(vesper-v2's route to the grammar for a concept product).
|
|
354
|
+
- Ground-or-greet extended to any progress-gated content on a pinned act, not
|
|
355
|
+
just engine cues.
|
|
356
|
+
- verify.md: two new measured failures (reduced-motion rail snap-centring a
|
|
357
|
+
single wide track; keyboard focus landing off-screen on pinned acts) and the
|
|
358
|
+
note that a shoot.mjs run can take the background server down with it.
|
|
359
|
+
|
|
360
|
+
## 2026-08-22: the `orrery` build (travel, continuous world)
|
|
361
|
+
|
|
362
|
+
First run of the skill start-to-finish as a new user would meet it: interview
|
|
363
|
+
first, nothing generated until the eight answers existed. Three findings, all
|
|
364
|
+
of them things that cost a full iteration to discover.
|
|
365
|
+
|
|
366
|
+
- **A scrim parented to the copy block is invisible to the contrast pass.**
|
|
367
|
+
`shoot.mjs` hides `[data-sc-copy],[data-sc-copy] *` to photograph the film
|
|
368
|
+
underneath a line, and `visibility: hidden` takes an element's `::before` with
|
|
369
|
+
it. A per-block scrim written as `.copy::before` is therefore never composited
|
|
370
|
+
into the measurement: six blocks reported 1.2-1.9:1 against means of 7-13:1,
|
|
371
|
+
and **strengthening the scrim changed the reported numbers by exactly zero**,
|
|
372
|
+
which is the tell. The identical-numbers-after-a-real-change signature is
|
|
373
|
+
worth knowing on its own. Fix: the scrim must be a **sibling** of the copy
|
|
374
|
+
block. `orrery` mounts one soft plate per block into the copy layer and drives
|
|
375
|
+
its opacity from the block's own inline opacity, so it tracks the engine's
|
|
376
|
+
window for free. Written up in taste.md and verify.md.
|
|
377
|
+
- **The worldflight spacer is sized once, inside mount, and a viewport that
|
|
378
|
+
reports height 0 at that moment leaves the page with no scroll track at all.**
|
|
379
|
+
The engine writes `height: 0px`, every mechanical check still passes, and the
|
|
380
|
+
page looks like a frozen still. Seen in an embedded preview pane; a single
|
|
381
|
+
dispatched `resize` corrected it to the exact expected 10.5vh. Page-level fix
|
|
382
|
+
(the engine is not edited): dispatch a resize on `load` and on
|
|
383
|
+
`document.fonts.ready`. That second one also absorbs the reflow when a webfont
|
|
384
|
+
swaps in, which changes every copy block's measured height.
|
|
385
|
+
- **`encode.sh` finds a full ffmpeg, but the poster step in `assets.md` calls
|
|
386
|
+
bare `ffmpeg`.** On a machine whose PATH ffmpeg is a stripped build, the
|
|
387
|
+
documented `-frames:v 1 ... poster.webp` fails with "Unable to choose an
|
|
388
|
+
output format", which reads like a filename problem rather than a missing
|
|
389
|
+
muxer. Use the same resolved binary `encode.sh` uses. Also: PSNR seam checks
|
|
390
|
+
need that binary, since the stripped build has no `psnr` filter.
|
|
391
|
+
|
|
392
|
+
Build notes worth carrying: the owner asked for a "tiny world" that does not
|
|
393
|
+
look cheap, which is one word away from the banned clay diorama. Resolving it as
|
|
394
|
+
a **handmade physical scale model** shot macro (brass, painted plaster, real
|
|
395
|
+
moss, poured resin, sifted sand, visible glue seams) satisfied both, and the
|
|
396
|
+
negative list in the preamble did the work. Ten legs chained head-to-tail on
|
|
397
|
+
pre-generated anchors rather than sequentially off encoded tails: every joint
|
|
398
|
+
came back 28.5-39.8 dB, inside the band `descent` shipped at, and all ten clips
|
|
399
|
+
generated **in parallel** instead of in a 45-minute serial chain.
|
|
400
|
+
|
|
401
|
+
Also confirmed the harness will happily photograph a **different site** if the
|
|
402
|
+
port is already occupied by another build's server. `serve.mjs` exits
|
|
403
|
+
EADDRINUSE in the background while `shoot.mjs` gets a clean 200 from the
|
|
404
|
+
squatter and produces a full, plausible, entirely wrong contact sheet. Check the
|
|
405
|
+
served `<title>` before trusting a run.
|
|
406
|
+
|
|
407
|
+
### Pacing pass, same day (owner note on `orrery`)
|
|
408
|
+
|
|
409
|
+
> "when you're building fly-through worlds like this, and most of the times when
|
|
410
|
+
> you're doing things with scroll, you need to slow it down a bit... it needs to
|
|
411
|
+
> feel smoother and consistent."
|
|
412
|
+
|
|
413
|
+
Two faults hide under "not smooth", and the skill only had guidance for one.
|
|
414
|
+
|
|
415
|
+
- **Inconsistent pace.** `orrery` shipped its first cut with leg weights from
|
|
416
|
+
0.70 to 0.95vh for identical 5-second clips, a 36% spread in how fast the
|
|
417
|
+
world moved per pixel. Nothing in the skill said to hold `weight / clip
|
|
418
|
+
seconds` constant, so nothing caught it. Now a hard rule in worldflight.md,
|
|
419
|
+
with `orrery` normalised to a 6% spread.
|
|
420
|
+
- **Too fast.** The `~1.5vh per 8s` line was being read as a target when it is a
|
|
421
|
+
dead-scroll guardrail. It yields 0.14-0.19vh per second of film, which is
|
|
422
|
+
brisk for a page the reader is steering. worldflight.md §7c now names
|
|
423
|
+
**0.21-0.22vh/s as the floor for a fly-through** and says plainly that
|
|
424
|
+
exceeding the guardrail is fine as long as the harness confirms no dead
|
|
425
|
+
scroll. It did, at both viewport sizes.
|
|
426
|
+
- **`data-sc-lerp` 0.18 is the wrong default for this mode.** 0.12 is now the
|
|
427
|
+
documented worldflight value, alongside a wider `data-sc-seam` (~0.16). The
|
|
428
|
+
damping is what removes wheel judder; the weights are what remove the surging.
|
|
429
|
+
|
|
430
|
+
feel.md §5 gained the exception it was missing: pacing variety is how an act
|
|
431
|
+
page carries feeling, and a continuous world is the one grammar where it is
|
|
432
|
+
wrong. There the peak carries the shape by being the single long leg.
|
|
433
|
+
|
|
434
|
+
Also recorded: changing any weight moves every leg boundary, so every
|
|
435
|
+
`data-sc-window` must be recomputed and then re-checked against the frame it was
|
|
436
|
+
tuned to. `orrery`'s labels were re-verified on screen at Kyoto and Erg Chebbi
|
|
437
|
+
after the repace.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Clip time is not cue time (the frozen-clip class)
|
|
442
|
+
|
|
443
|
+
Reported from a real read of `agency` / Fallowbank: "scenes are moving with a
|
|
444
|
+
scroll, but then they stop and then the whole site starts to move, so now we're
|
|
445
|
+
seeing an image that's still."
|
|
446
|
+
|
|
447
|
+
A pinned stage is on screen for one viewport **before** its pinned travel starts
|
|
448
|
+
and one viewport **after** it ends. Act progress `p` is 0 across the whole entry
|
|
449
|
+
slide and 1 across the whole exit slide, so a clip driven by `p` is parked on its
|
|
450
|
+
first frame while the stage slides in and on its last frame while it slides out.
|
|
451
|
+
The reader scrubs a film, the film stops, and the page then slides a still
|
|
452
|
+
photograph past them.
|
|
453
|
+
|
|
454
|
+
**What changed**
|
|
455
|
+
|
|
456
|
+
- **The engine now maps clip time across the stage's entire visible life by
|
|
457
|
+
default**, with both ends clamped to scroll that actually exists so a hero at
|
|
458
|
+
document top still starts on frame one and an act near the bottom still reaches
|
|
459
|
+
its last frame. Cues still use `p`, because cues belong to the pin.
|
|
460
|
+
`data-sc-clip-map="travel"` is the opt-out. The old `data-sc-runout` opt-in is
|
|
461
|
+
accepted and ignored: it addressed only the exit half, and it was opt-in, which
|
|
462
|
+
is why three of four builds carrying a scrub act shipped frozen anyway.
|
|
463
|
+
- **`shoot.mjs` samples each scrub act's entry and exit slides.** It previously
|
|
464
|
+
sampled pinned acts only at `top + (h - vh) * p`, which never visits either
|
|
465
|
+
slide. That sampling gap is the whole reason this survived four builds of
|
|
466
|
+
verification: the defect lived precisely where the harness never looked.
|
|
467
|
+
- **New FROZEN CLIP finding**, graded against stage visibility rather than act
|
|
468
|
+
progress. First/last-frame holds always report; mid-clip holds only report once
|
|
469
|
+
they outlast a plausible `dwell` settle. Skipped under reduced motion.
|
|
470
|
+
|
|
471
|
+
**Validated, not assumed.** The detector was proven to discriminate before being
|
|
472
|
+
trusted: it fires on a clip deliberately opted out with `data-sc-clip-map="travel"`
|
|
473
|
+
and stays silent on the clip beside it in the same page.
|
|
474
|
+
|
|
475
|
+
**Do not pair this with a shorter dwell.** Dwell moves fast at the edges and
|
|
476
|
+
settles in the middle, which is exactly the shape the new mapping wants: fast
|
|
477
|
+
motion on the two slides, the settle inside the pin where the copy lands.
|
|
478
|
+
|
|
479
|
+
**Also found, unrelated and pre-existing:** `nateherk`'s hero paragraph fails
|
|
480
|
+
contrast from `y=0` (1.01:1, dark ink over dark foliage in the sky clip). The new
|
|
481
|
+
slide sampling surfaced it; the old engine fails it identically, so it is not a
|
|
482
|
+
regression from this change.
|
|
483
|
+
|
|
484
|
+
## 2026-08-22, portability pass (v0.2.0)
|
|
485
|
+
|
|
486
|
+
The skill assumed one person's repo layout. Five references pointed at
|
|
487
|
+
`OtherWorlds/Ultimate Websites/`, so the fingerprint gate and the worked
|
|
488
|
+
worldflight rig dead-ended on every other machine. Packaging it as a plugin made
|
|
489
|
+
that a real defect rather than a note.
|
|
490
|
+
|
|
491
|
+
The fix is **not** a second templatised copy of the skill. Two copies drift, and
|
|
492
|
+
this one proved it inside a day: five files diverged between the working skill
|
|
493
|
+
and its published copy within an hour of the first push. There is one skill, and
|
|
494
|
+
it resolves its paths instead of assuming them.
|
|
495
|
+
|
|
496
|
+
- **`scripts/workspace.mjs`.** Resolves the workspace that holds builds and the
|
|
497
|
+
registry: `SCROLLCRAFT_HOME`, then the nearest `.scrollcraft.json` walking up,
|
|
498
|
+
then `<project root>/scrollcraft`. `--ensure` creates it and seeds an empty
|
|
499
|
+
registry. Builds are `<workspace>/builds/<name>/`, the registry is
|
|
500
|
+
`<workspace>/FINGERPRINTS.md`, and neither is hardcoded anywhere any more.
|
|
501
|
+
- **The registry is per-user and starts empty.** The gate exists to stop you
|
|
502
|
+
repeating *yourself*, so a new user's first build has nothing to clear and
|
|
503
|
+
every build after it does. Gating a newcomer against somebody else's twelve
|
|
504
|
+
rows would block them out of grammars they have never used, which is the
|
|
505
|
+
opposite of the point. `templates/FINGERPRINTS.md` is the seed; the author's
|
|
506
|
+
twelve-row table ships separately as `EXAMPLES.md`, explicitly as
|
|
507
|
+
illustration rather than constraint.
|
|
508
|
+
- **`scripts/doctor.mjs`.** Preflight, run before the interview. Checks node, a
|
|
509
|
+
full ffmpeg build, playwright, Chrome, the API key and the resolved workspace,
|
|
510
|
+
and separates required failures from optional ones. It exists because the
|
|
511
|
+
three most common setup faults all surface later as misleading errors: a
|
|
512
|
+
stripped ffmpeg reports a missing filter as a syntax error, a missing webp
|
|
513
|
+
muxer reports as a bad filename, and playwright resolves from the wrong
|
|
514
|
+
directory. On the author's machine it correctly picks the 585-filter build
|
|
515
|
+
over the stripped one first on PATH.
|
|
516
|
+
- **`scripts/worldflight-assert.mjs` now ships with the skill.** It was
|
|
517
|
+
URL-driven and generic all along, sitting in a lab folder nobody else had.
|
|
518
|
+
Run it against any worldflight page before the contact sheet.
|
|
519
|
+
- **The API key is documented as optional in the right place.** Generation costs
|
|
520
|
+
real money; a build from the user's own photos and footage needs no key and no
|
|
521
|
+
spend, and that is now stated as a first-class route in Bootstrap rather than
|
|
522
|
+
implied.
|
|
523
|
+
|
|
524
|
+
Nothing moved on the author's machine: a two-line `.scrollcraft.json` at the
|
|
525
|
+
project root points the workspace at the existing `OtherWorlds/Ultimate
|
|
526
|
+
Websites`, so twelve builds and the live registry resolve exactly as before.
|