Thanks for the skill — I ran it end to end and shipped a page with it (yepgent.com/world/, 6 scenes, architecture A). Two issues made the page render as static posters in production while every local check passed. Both are environmental, so local QA cannot see them.
Happy to open a PR for any of this if you'd like it.
1. blob: media needs an explicit CSP media-src
The engine's blob-loading strategy is the right call — it's what keeps clips seekable on hosts without byte-range support. But on any site with a CSP, blob: is not covered by default-src 'self'. Result:
MEDIA_ELEMENT_ERROR: Media load rejected by URL safety check
readyState 0, videoWidth 0, every clip dead, page renders as six posters. Reproduced by serving the same build with the production CSP string, then fixed by adding one directive:
|
prod CSP |
+ media-src 'self' blob: |
readyState |
0 |
4 |
videoWidth |
0 |
1280 |
currentTime on scroll |
0 → 0 |
3.66 → 5.01 |
Why it evades QA: python -m http.server sends no CSP header at all, so the dev surface is strictly more permissive than production.
Suggested: a Step 0 preflight that curls the deploy target and asserts media-src names blob: (and greps netlify.toml / _headers / vercel.json), plus a line in SKILL.md Gotchas. This is cheap to check before generating and expensive to discover after.
2. prefers-reduced-motion leaves no way in
Under reduced motion the engine creates zero <video> elements and leaves the posters up. On a page whose only content is the camera flight, that's a dead end rather than an accommodation — the visitor gets a page that does nothing, with no affordance suggesting otherwise.
Why it evades QA: Playwright's bundled Chromium reports prefers-reduced-motion: false. I only found it by instrumenting the actual user's Chrome over CDP: {reduced: true, videos: 0}.
Suggested: keep the OS setting as the default, but let the host page offer an explicit opt-in. A one-line change makes this possible:
const reduce = config.forceMotion ? false
: window.matchMedia('(prefers-reduced-motion: reduce)').matches;
The page then renders a small bar ("Your system asks for reduced motion… [Play the flight]") that re-mounts with forceMotion: true. Worth noting in the docs that anything injected after mount must re-run on that second mount.
3. Smaller things
- Poster = frame 0 is wrong when a leg round-trips. With
seedance_2_0 I found legs that end where they began (see below), which makes frame 0 the previous scene's arrival frame — so the reduced-motion fallback illustrated each section with the wrong picture. Using each section's resting frame fixed both that and the paint-in pop.
cta.href: '#finale' in index-template.html matches nothing — the engine never assigns section ids to any element, so the header CTA is inert.
- The handoff clause
[the doorway / opening / direction of the next scene] — the third option is a trap. Describing the next scene vividly made the model spend the whole leg arriving there and skip the scene it was meant to traverse. Worth narrowing the template to a direction only.
- Model tiers fail in opposite directions.
seedance_2_0_mini under-weights the start image (chases the described destination); seedance_2_0 over-weights it (won't leave the start frame's world, and ambiguous directional referents that match something in the start frame resolve backward). A fix tuned on one tier can break the other — might be worth flagging in the Step 4 table.
Great skill overall — the frame-locked chain genuinely works, and seams were invisible once the prompts were right.
Thanks for the skill — I ran it end to end and shipped a page with it (yepgent.com/world/, 6 scenes, architecture A). Two issues made the page render as static posters in production while every local check passed. Both are environmental, so local QA cannot see them.
Happy to open a PR for any of this if you'd like it.
1.
blob:media needs an explicit CSPmedia-srcThe engine's blob-loading strategy is the right call — it's what keeps clips seekable on hosts without byte-range support. But on any site with a CSP,
blob:is not covered bydefault-src 'self'. Result:readyState 0,videoWidth 0, every clip dead, page renders as six posters. Reproduced by serving the same build with the production CSP string, then fixed by adding one directive:media-src 'self' blob:readyStatevideoWidthcurrentTimeon scrollWhy it evades QA:
python -m http.serversends no CSP header at all, so the dev surface is strictly more permissive than production.Suggested: a Step 0 preflight that curls the deploy target and asserts
media-srcnamesblob:(and grepsnetlify.toml/_headers/vercel.json), plus a line in SKILL.md Gotchas. This is cheap to check before generating and expensive to discover after.2.
prefers-reduced-motionleaves no way inUnder reduced motion the engine creates zero
<video>elements and leaves the posters up. On a page whose only content is the camera flight, that's a dead end rather than an accommodation — the visitor gets a page that does nothing, with no affordance suggesting otherwise.Why it evades QA: Playwright's bundled Chromium reports
prefers-reduced-motion: false. I only found it by instrumenting the actual user's Chrome over CDP:{reduced: true, videos: 0}.Suggested: keep the OS setting as the default, but let the host page offer an explicit opt-in. A one-line change makes this possible:
The page then renders a small bar ("Your system asks for reduced motion… [Play the flight]") that re-mounts with
forceMotion: true. Worth noting in the docs that anything injected after mount must re-run on that second mount.3. Smaller things
seedance_2_0I found legs that end where they began (see below), which makes frame 0 the previous scene's arrival frame — so the reduced-motion fallback illustrated each section with the wrong picture. Using each section's resting frame fixed both that and the paint-in pop.cta.href: '#finale'inindex-template.htmlmatches nothing — the engine never assigns sectionids to any element, so the header CTA is inert.[the doorway / opening / direction of the next scene]— the third option is a trap. Describing the next scene vividly made the model spend the whole leg arriving there and skip the scene it was meant to traverse. Worth narrowing the template to a direction only.seedance_2_0_miniunder-weights the start image (chases the described destination);seedance_2_0over-weights it (won't leave the start frame's world, and ambiguous directional referents that match something in the start frame resolve backward). A fix tuned on one tier can break the other — might be worth flagging in the Step 4 table.Great skill overall — the frame-locked chain genuinely works, and seams were invisible once the prompts were right.