@ericrisco/rsc 3.1.3 → 3.3.0

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 (105) hide show
  1. package/README.md +8 -6
  2. package/manifest.json +43 -69
  3. package/package.json +6 -1
  4. package/scripts/design-upstream.js +92 -0
  5. package/scripts/doctor.js +0 -24
  6. package/scripts/lib/default-skill-floor.js +5 -1
  7. package/scripts/lib/domains.js +2 -2
  8. package/scripts/lib/recommend.js +1 -3
  9. package/scripts/lib/retired-skills.js +7 -1
  10. package/skills/ai-media/SKILL.md +2 -2
  11. package/skills/design/IMPECCABLE-LICENSE.txt +191 -0
  12. package/skills/design/SKILL.md +79 -328
  13. package/skills/design/THIRD_PARTY.md +26 -0
  14. package/skills/design/engine/LICENSE +21 -0
  15. package/skills/design/engine/RUN.md +515 -0
  16. package/skills/design/engine/UPSTREAM.json +4 -0
  17. package/skills/design/engine/assets/app.css +216 -0
  18. package/skills/design/engine/assets/base.css +105 -0
  19. package/skills/design/engine/assets/base.js +37 -0
  20. package/skills/design/engine/assets/charts.js +617 -0
  21. package/skills/design/engine/assets/scenes/stage.css +28 -0
  22. package/skills/design/engine/assets/scenes/stage.js +133 -0
  23. package/skills/design/engine/library/app.json +1327 -0
  24. package/skills/design/engine/library/directions.json +1078 -0
  25. package/skills/design/engine/library/heroes.json +1086 -0
  26. package/skills/design/engine/library/motions.json +92 -0
  27. package/skills/design/engine/library/palettes.json +1485 -0
  28. package/skills/design/engine/library/photos.json +7240 -0
  29. package/skills/design/engine/library/sections.json +4429 -0
  30. package/skills/design/engine/library/sites.json +8350 -0
  31. package/skills/design/engine/library/type.json +2024 -0
  32. package/skills/design/engine/references/app-screens.md +1199 -0
  33. package/skills/design/engine/references/colour.md +141 -0
  34. package/skills/design/engine/references/copy.md +177 -0
  35. package/skills/design/engine/references/critique.md +182 -0
  36. package/skills/design/engine/references/imagery.md +791 -0
  37. package/skills/design/engine/references/layout.md +292 -0
  38. package/skills/design/engine/references/motion.md +980 -0
  39. package/skills/design/engine/references/research.md +356 -0
  40. package/skills/design/engine/references/scenes.md +174 -0
  41. package/skills/design/engine/references/tells.md +115 -0
  42. package/skills/design/engine/references/type.md +170 -0
  43. package/skills/design/engine/scripts/assets.mjs +865 -0
  44. package/skills/design/engine/scripts/draw.mjs +584 -0
  45. package/skills/design/engine/scripts/inspect.mjs +303 -0
  46. package/skills/design/engine/scripts/lib/app-draw.mjs +203 -0
  47. package/skills/design/engine/scripts/lib/cdp.mjs +248 -0
  48. package/skills/design/engine/scripts/lib/probe.mjs +909 -0
  49. package/skills/design/engine/scripts/lib/rules.mjs +243 -0
  50. package/skills/design/engine/scripts/lint.mjs +119 -0
  51. package/skills/design/engine/scripts/scenes.mjs +124 -0
  52. package/skills/design/evals/cases.yaml +35 -46
  53. package/skills/design/references/design-md.md +49 -0
  54. package/skills/design/references/finish-review.md +46 -0
  55. package/skills/design/references/modes.md +51 -0
  56. package/skills/expo/SKILL.md +3 -4
  57. package/skills/marketing/references/landing-copy.md +2 -2
  58. package/skills/react/SKILL.md +2 -2
  59. package/skills/react-native/SKILL.md +3 -3
  60. package/skills/scroll-film/SKILL.md +220 -0
  61. package/skills/scroll-film/assets/page-template.html +128 -0
  62. package/skills/scroll-film/assets/scrub-engine.js +352 -0
  63. package/skills/scroll-film/evals/README.md +26 -0
  64. package/skills/scroll-film/evals/cases.yaml +81 -0
  65. package/skills/scroll-film/references/chain.md +218 -0
  66. package/skills/scroll-film/references/engine.md +132 -0
  67. package/skills/scroll-film/scripts/verify.sh +158 -0
  68. package/skills/swift-ios/SKILL.md +1 -1
  69. package/skills/ui-engineering/SKILL.md +3 -4
  70. package/skills/ui-engineering/evals/cases.yaml +2 -2
  71. package/skills/variant-explorer/SKILL.md +4 -4
  72. package/skills/variant-explorer/evals/cases.yaml +1 -1
  73. package/targets/clone-bootstrap.mjs +1 -1
  74. package/scripts/lib/design-identity.js +0 -117
  75. package/scripts/lib/starting-point.js +0 -314
  76. package/skills/design/evals/README.md +0 -53
  77. package/skills/design/references/ai-tells.md +0 -75
  78. package/skills/design/references/brand-grounding.md +0 -197
  79. package/skills/design/references/copywriting-frameworks.md +0 -138
  80. package/skills/design/references/design-systems.md +0 -49
  81. package/skills/design/references/inspiration-sources.md +0 -96
  82. package/skills/design/references/landing-anatomy-and-cro.md +0 -202
  83. package/skills/design/references/motion-and-interaction.md +0 -228
  84. package/skills/design/references/research-method.md +0 -154
  85. package/skills/design/references/signature-and-craft.md +0 -148
  86. package/skills/design/references/starting-point.md +0 -75
  87. package/skills/design/references/trends-2026.md +0 -80
  88. package/skills/design/references/visual-system.md +0 -237
  89. package/skills/design/scripts/verify.sh +0 -357
  90. package/skills/design-dna/PROMPT.md +0 -211
  91. package/skills/design-dna/SKILL.md +0 -162
  92. package/skills/design-dna/design-dna.schema.json +0 -230
  93. package/skills/design-dna/evals/README.md +0 -64
  94. package/skills/design-dna/evals/cases.yaml +0 -68
  95. package/skills/design-dna/references/method.md +0 -436
  96. package/skills/design-dna/scripts/emit.py +0 -164
  97. package/skills/design-dna/templates/PROMPT.md.tmpl +0 -42
  98. package/skills/design-dna/templates/SKILL.md.tmpl +0 -42
  99. package/skills/design-dna/templates/check.py.tmpl +0 -143
  100. package/skills/design-loop/SKILL.md +0 -160
  101. package/skills/design-loop/evals/README.md +0 -56
  102. package/skills/design-loop/evals/cases.yaml +0 -60
  103. package/skills/motion-craft/SKILL.md +0 -125
  104. package/skills/motion-craft/evals/cases.yaml +0 -38
  105. package/skills/motion-craft/scripts/verify.sh +0 -75
@@ -1,56 +1,45 @@
1
1
  skill: design
2
2
 
3
3
  should_trigger:
4
- - prompt: "Design a landing page for our new analytics SaaS — hero, pricing, the works."
5
- why: "Explicit landing-page design with full marketing-page composition; this is the skill's core use case."
6
- - prompt: "Our homepage looks generic, like every other AI-generated startup site. Can you make it feel premium and intentional?"
7
- why: "Rescuing a 'feels generic / AI-slop' UI is named verbatim in the description and the When-to-use list."
8
- - prompt: "Pick a color palette and type pairing for my product, and give me the Tailwind tokens."
9
- why: "Choosing a visual system (color, type, tokens) is an explicit trigger; the skill ships Tailwind v4 @theme OKLCH tokens."
10
- - prompt: "Can you do a design review of this hero section and tell me how to make it convert better?"
11
- why: "Design review / critique plus conversion intent; the skill has a dedicated design-review QA checklist and a 0-10 visual-audit rubric."
12
- - prompt: "I've got the React components built and working, but it all just looks flat and amateur — help me polish the visuals and spacing."
13
- why: "A polish pass on an existing surface; user names the symptom (flat/amateur) without naming a tool, which the skill targets ('make an interface feel premium')."
14
- - prompt: "Write the hero headline and a punchy value prop for our developer-tool site, and lay out the page so it sells."
15
- why: "Conversion copy (hero/value-prop) plus page layout for selling — the skill owns visual + landing structure and co-owns copy; non-obvious because it leads with copy."
16
- - prompt: "Add a bento-grid features section to our marketing site that doesn't look like a template."
17
- why: "'bento section' is an explicit named trigger; involves layout/composition for a marketing surface."
4
+ - prompt: "Design a landing page for our new analytics SaaS."
5
+ why: "A public page to design from scratch is the page run's core case (Persuade mode)."
6
+ - prompt: "Our homepage looks generic, like every other AI-generated startup site. Make it look designed."
7
+ why: "'Looks generic / AI-made' is named in the description; the skill exists to replace the model's default."
8
+ - prompt: "Hazme la web de un restaurante de mi pueblo, que no parezca una plantilla."
9
+ why: "A local business page in Spanish; the inventory of the subject and real pictures are exactly what the run forces."
10
+ - prompt: "Rediseña el dashboard de facturación, que se ve feo y desordenado."
11
+ why: "An app screen redesign (Operate mode) follows the app-screen run with its own draw and inspector."
12
+ - prompt: "Review this portfolio page and tell me the three changes that would improve it most."
13
+ why: "A review of an existing page: record the before in the browser, critique the screenshots, stop at findings."
14
+ - prompt: "Restyle our docs site so it stops looking like every other docs template."
15
+ why: "Read mode: the style owns the frame while the reading column stays calm."
18
16
 
19
17
  should_not_trigger:
20
- - prompt: "Write the SEO keyword strategy and do a technical SEO audit for our marketing site, targeting buyer-intent terms."
21
- route_to: "marketing"
22
- why: "Deep keyword research, GEO, and technical SEO audits are explicitly delegated to the marketing sibling; design only enforces SEO-aware markup structure."
23
- - prompt: "Set up the Next.js 15 App Router routing, server actions, and data fetching for these pages."
18
+ - prompt: "Write the hero headline, offer and CTA copy for our pricing page."
19
+ route_to: "landing-copy"
20
+ why: "The words on a conversion page belong to landing-copy; design builds the page around them."
21
+ - prompt: "Where should the filter state live, in the URL or in the component, and how do we handle loading and errors?"
22
+ route_to: "ui-engineering"
23
+ why: "How interface is built (state, boundaries, async states) is ui-engineering, not visual design."
24
+ - prompt: "Make our web app pass WCAG 2.2 AA: fix the axe violations and keyboard traps."
25
+ route_to: "accessibility"
26
+ why: "Conformance remediation is the accessibility skill's job."
27
+ - prompt: "Create our logo brief and a full brand book with print colours."
28
+ route_to: "brand-identity"
29
+ why: "A brand system for every medium is brand-identity; design consumes it with --keep palette,type."
30
+ - prompt: "Set up Next.js App Router routing and server actions for these pages."
24
31
  route_to: "nextjs"
25
- why: "Pure App Router / React 19 build mechanics are the nextjs sibling's job; design hands off the build."
26
- - prompt: "Build a daily-use internal ops dashboard for our support team to triage tickets fast — pure data tables, no marketing."
27
- route_to: "none"
28
- why: "Dense internal operational tooling is explicitly a do-NOT-force-a-landing case; the skill says not to paint a marketing skin on a daily-use tool. Routes to product judgment, not a sibling."
29
- - prompt: "Mirror our brand colors and typography into the Flutter mobile app's theme."
30
- route_to: "flutter"
31
- why: "Translating brand tokens into a Flutter app is the flutter sibling's job, not web UI design."
32
- - prompt: "Implement an iOS 26 SwiftUI Liquid Glass material for this native settings panel."
33
- route_to: "none"
34
- why: "Native iOS Liquid Glass is the external liquid-glass-design reference; this skill ships only the web glass approximation, so it should decline/defer (no in-repo sibling)."
35
- - prompt: "Help me wire up the spring physics and AnimatePresence layout animations for this modal in Framer Motion."
36
- route_to: "none"
37
- why: "Deep motion-code mechanics (springs, AnimatePresence internals) are explicitly deferred to the external motion-ui references; design only sets motion intent + budget."
32
+ why: "Framework build mechanics are nextjs."
38
33
 
39
34
  capability:
40
- - scenario: "User asks: 'Design the hero section for our new SaaS landing page and make it look premium.' (No 02-DOCS/wiki/brand/ study exists in the repo yet.)"
35
+ - scenario: "Asked to build a landing page for a bakery with no brand and no photographs."
41
36
  must_include:
42
- - "STOPS before designing and checks for a brand study at 02-DOCS/wiki/brand/ (via root CLAUDE.md ## Brand & voice); on finding it missing/incomplete, asks a focused batch of brand-grounding questions instead of guessing."
43
- - "Runs (or states it will run) the research-first protocol: WebSearch award galleries / tier-1 sites and synthesize a cited DESIGN DIRECTION before prescribing, rather than shipping from memory."
44
- - "Enforces the 5-second value-prop test: hero states what it is, who it's for, why it's better, above the fold — and shows the product, not a centered headline over an atmospheric gradient."
45
- - "Uses design tokens (Tailwind v4 @theme, OKLCH color, modular type scale, 4/8px spacing, radius/shadow tokens) — no magic hex or arbitrary px."
46
- - "Treats accessibility as a hard constraint: text contrast >= 4.5:1, visible focus, one <h1>, semantic landmarks, touch targets >= 24px (44px ideal)."
47
- - "Treats Core Web Vitals as design constraints: LCP < 2.5s with next/image priority on the hero image, INP < 200ms, CLS < 0.1, next/font."
48
- - "Benefit-led, specific copy that avoids the ban-list (revolutionary, seamless, game-changer, unlock, supercharge, elevate, etc.)."
49
- - scenario: "User asks: 'Here's our current pricing page — do a design review and tell me how good it actually is.'"
37
+ - "inventory of eight to twelve things only this client has"
38
+ - "draw.mjs deals three directions; one is chosen against the inventory with a written reason"
39
+ - "an idea sentence that fails the competitor test, and one live thing"
40
+ - "at least four real pictures found with assets.mjs and credited"
41
+ - "inspect.mjs at 1440, 768 and 390 with zero FAIL before reporting"
42
+ - scenario: "The machine has no Chrome and Node 20."
50
43
  must_include:
51
- - "Applies the design-review QA checklist (value prop in 5s, contrast >= 4.5:1, visible focus, one <h1>, semantic landmarks, LCP image priority, next/font, no transition: all, tokens not magic numbers, ban-list-clean copy, 360px fit, empty/loading/error states)."
52
- - "Scores the 11-dimension visual-audit rubric 0-10 with weights and reports the weighted total on its /115 scale, mapped to a band (<69 generic / 69-91 competent / 92-108 premium / 109+ award-tier)."
53
- - "For each dimension scoring below 8, gives a concrete actionable fix (e.g. exact spacing/token change), not a vague 'improve spacing'."
54
- - "Checks copy against the ban-list and flags hype/vague value props that fail the 5s test."
55
- - "Flags any transition: all, magic hex/px, cards-in-cards, or purple->blue everything gradient as defects, not preferences."
56
- - "Ties findings back to the brand study / cited trend source where the bar is set, rather than asserting taste."
44
+ - "runs lint.mjs and critiques screenshots from another browser tool"
45
+ - "the report says 'inspector not run' with the reason and claims no gate pass"
@@ -0,0 +1,49 @@
1
+ # DESIGN.md: the record of the system
2
+
3
+ Adapted from impeccable's `document` by Paul Bakaus (Apache-2.0), which follows the open
4
+ `design.md` format. Rewritten and shortened.
5
+
6
+ The point: the next edit, the next session and the next tool keep the look instead of drifting back
7
+ to the model's default. Write it **after** the build, from what was actually built and measured,
8
+ never before. A rulebook written ahead of the build gets defended against reality.
9
+
10
+ ## Where and when
11
+
12
+ - `DESIGN.md` at the project root, committed with the code.
13
+ - Written when a surface creates or replaces the visual system. Extending an existing surface reads
14
+ it and does not rewrite it.
15
+ - If `02-DOCS/wiki/brand/` exists, `DESIGN.md` uses the brand's colours and faces and names that
16
+ file as its source.
17
+
18
+ ## Shape
19
+
20
+ YAML front matter holds the tokens; the body explains them.
21
+
22
+ ```markdown
23
+ ---
24
+ name: <product>
25
+ description: <one line: the idea sentence>
26
+ colors:
27
+ ground: "#…"
28
+ ink: "#…"
29
+ accent: "#…"
30
+ field: "#…"
31
+ typography:
32
+ display: { fontFamily: "…", fontWeight: 600, letterSpacing: "-0.03em", lineHeight: 0.95 }
33
+ text: { fontFamily: "…", fontSize: "18px", lineHeight: 1.5 }
34
+ rounded: { button: "0px", media: "0px" }
35
+ spacing: { margin: "24px", section: "200px" }
36
+ direction: { id: "<dealt id>", seed: <n> }
37
+ ---
38
+
39
+ ## Overview the idea, the mode, the direction and why it was chosen
40
+ ## Colors roles and proportions (one ground, one ink, one accent, fields)
41
+ ## Typography the scale actually used, tracking, measure
42
+ ## Layout grid, outer margins, section rhythm, the phone composition
43
+ ## Imagery sources, crops, treatment, where credits live
44
+ ## Motion the signature move and the entrance recipe
45
+ ## Components buttons, navigation, the live thing, footer
46
+ ## Do's and Don'ts the house rules this project relies on, plus any `--allow` and its reason
47
+ ```
48
+
49
+ Values come from the built CSS and the inspector's measurements, not from memory.
@@ -0,0 +1,46 @@
1
+ # The fresh review
2
+
3
+ Adapted from impeccable's finish reviewer by Paul Bakaus (Apache-2.0); rewritten and shortened.
4
+
5
+ The builder believes its own page. The review runs **outside** the build: a fresh subagent with no
6
+ history of this conversation, so it does not inherit the builder's framing or optimism. Without
7
+ subagents, step fully out of the build and run the same checks yourself, and say in the report that
8
+ the review was not independent.
9
+
10
+ ## The packet
11
+
12
+ Hand it only this:
13
+
14
+ - the original request and the user's answers;
15
+ - the screenshots from `.taste-skill/inspect/` at every width (the reviewer has no browser);
16
+ - the last `inspect.mjs` result;
17
+ - the direction sheet (`draw.mjs --show`), the idea sentence and the live thing;
18
+ - the paths of `../engine/RUN.md` (house rules) and `../engine/references/critique.md`.
19
+
20
+ ## The checks, in order
21
+
22
+ 0. **Evidence.** Every width captured, no blank or half-loaded screenshot, content where the
23
+ filename says. If one fails, the answer is `recapture` and nothing else.
24
+ 1. **First screen.** Within five seconds: what is this, why does it matter, what do I do?
25
+ 2. **The idea.** Is the idea sentence visible on the page, in every section, and does it fail the
26
+ competitor test (would it work for the nearest competitor)?
27
+ 3. **The direction.** Was the dealt direction built, or did the build drift back to the layout the
28
+ model would have made anyway?
29
+ 4. **Substance.** Real pictures, full content, no screen that is mostly empty ground.
30
+ 5. **Truth.** No invented customers, quotes, numbers or logos. Sample content marked.
31
+ 6. **Floor.** The house rules in `../engine/RUN.md`, plus what screenshots show and code hides:
32
+ states (hover, focus, loading, empty, error), themed selection and focus rings, text that fits.
33
+
34
+ ## The four words
35
+
36
+ The first line of the answer is exactly one of these. The word follows from the checks:
37
+
38
+ - `recapture`: the evidence failed, not the page. Capture again, then a full review.
39
+ - `rebuild`: the focal element or the direction itself is wrong. Re-derive it; patches will not do.
40
+ - `fix`: an ordered list of material fixes, at most eight, most important first.
41
+ - `ship`: nothing material remains.
42
+
43
+ Then: `fixes` (when `fix`) and `keep`, one line naming what must not be diluted while fixing.
44
+ Apply the fixes in one batch, run the gate again, and send the new screenshots back for a verdict
45
+ on each fix: resolved, partial or unresolved. Two rounds at most. A partial or unresolved fix
46
+ never becomes `ship`. Report the reviewer's word as it was given.
@@ -0,0 +1,51 @@
1
+ # The four modes
2
+
3
+ Adapted from impeccable by Paul Bakaus (Apache-2.0); rewritten and shortened. The mode names what
4
+ the visitor's success looks like on this surface. Pick it from the surface, never the product: a
5
+ developer tool's landing page is Persuade, a fashion house's docs are Read.
6
+
7
+ ## Persuade
8
+
9
+ The visitor decides and acts. The design is what they judge.
10
+
11
+ - The first screen makes the offer clear and shows **the action visitors came to take, working**: a
12
+ flight search, open appointment slots, add to cart. A button that points somewhere else is not it.
13
+ - The subject's world carries the page; it is never painted over it as a poster with no working parts.
14
+ - Refuse both ends: the poster, and the stock product page (headline, subline, button, a row of
15
+ equal cards; or the split hero with a badge row under it).
16
+
17
+ ## Experience
18
+
19
+ The visitor is inside the work: portfolio, gallery, showcase. The work leads from the first
20
+ viewport and the interface recedes. Same page run as Persuade.
21
+
22
+ ## Operate
23
+
24
+ The visitor completes a task. Someone who uses tools like this should know where everything is
25
+ before they notice the style.
26
+
27
+ - The direction lends four things only: **type, palette, density and one signature move** (service
28
+ health drawn as a transit map, a schedule in a timetable's column rhythm). Never the layout, the
29
+ navigation model or the controls.
30
+ - Every control is the standard one the visitor already knows. No costume of a terminal, an
31
+ instrument bezel or ledger paper.
32
+ - Colour does real jobs: a committed shell, state colours, data series, selection. A grey screen
33
+ with one accent is the category default without its polish.
34
+ - Realistic data with units and one state worth seeing (an alert, a degraded service, an empty
35
+ filter). Never grey placeholder bars.
36
+ - The bar is the cleanest product in its category; the signature move adds to that, never replaces it.
37
+
38
+ The run, rules and checks for these screens: `../engine/references/app-screens.md`
39
+ (`draw.mjs --app`, `inspect.mjs --app`).
40
+
41
+ ## Read
42
+
43
+ The visitor understands something: docs, guide, article, changelog.
44
+
45
+ - The style owns **the frame**: masthead, rail, ground, title and section openers, how code and
46
+ tables are set. That is where palette, type and the signature move commit fully.
47
+ - The reading column stays calm: a reading face, real contrast, about 60–75 characters at about
48
+ 16px. No monospace prose. Nothing performs behind the text.
49
+ - The style never supplies the reading order or replaces the navigation with something to learn.
50
+ - Check: can a reader tell where they are, where to go next, and read comfortably? Cover the name:
51
+ could this be any other product's docs? Then the frame has not taken the style yet.
@@ -8,10 +8,9 @@ origin: risco
8
8
 
9
9
  # Expo & EAS: shipping React Native
10
10
 
11
- **Hand-off — the app vs its motion.** This skill owns the Expo platform: router, config plugins, EAS,
12
- native modules, store builds. **Animation and gestures** — Reanimated worklets, the UI runtime,
13
- sheets, screen transitions, press feedback, haptics, motion that stutters on a real device — are
14
- `../motion-craft/SKILL.md`'s.
11
+ **Scope.** This skill owns the Expo platform: router, config plugins, EAS, native modules, store
12
+ builds. Animation and gestures (Reanimated worklets, sheets, press feedback, haptics) follow
13
+ `../react-native/SKILL.md`. Visual design of web pages and screens is `../design/SKILL.md`'s.
15
14
 
16
15
 
17
16
  ## What this skill owns
@@ -1,6 +1,6 @@
1
1
  # Landing Copy by Section
2
2
 
3
- The words for every landing section, mapped 1:1 to the `design` skill's section anatomy (`../../design/references/landing-anatomy-and-cro.md`). `design` owns the visual treatment and layout; this file owns the copy that goes inside each section. Each section has **one copy job**; cut any section whose job you can't name. Ground every line in the brand study (`brand-grounding.md`) and run the value prop through the canvas (`copy-frameworks.md`) first.
3
+ The words for every landing section. `../../design/SKILL.md` owns the visual treatment, the layout and which sections exist (its argument step: one claim per section); this file owns the copy that goes inside each section. Each section has **one copy job**; cut any section whose job you can't name. Ground every line in the brand study (`brand-grounding.md`) and run the value prop through the canvas (`copy-frameworks.md`) first.
4
4
 
5
5
  ## Section map (copy job + framework)
6
6
 
@@ -187,5 +187,5 @@ Good — "Stop babysitting staging. Start free — no credit card."
187
187
 
188
188
  - `copy-frameworks.md` — the frameworks and headline formulas each section uses.
189
189
  - `brand-grounding.md` — the value prop, proof, and voice every line is grounded in.
190
- - `../../design/references/landing-anatomy-and-cro.md` — the visual anatomy these words fill.
190
+ - `../../design/SKILL.md` — the page these words fill; write the copy deck before its markup.
191
191
  - `campaigns-and-channels.md` — driving traffic to this page.
@@ -10,8 +10,8 @@ origin: risco
10
10
 
11
11
  **Hand-off — picking dependencies.** Which library to reach for (UI primitives, command menus,
12
12
  toasts, OTP inputs, charts, virtualization, drag and drop, state, styling, theming) is
13
- `../ui-engineering/SKILL.md`'s — build vs adopt is decided there, before hand-rolling a component. Animating what you
14
- install is `../motion-craft/SKILL.md`'s.
13
+ `../ui-engineering/SKILL.md`'s — build vs adopt is decided there, before hand-rolling a component. The look of a page
14
+ or screen, and its motion layer, is `../design/SKILL.md`'s.
15
15
 
16
16
 
17
17
  > Build or review a fast, typed, correctly-architected **client-side** React single-page app bundled by Vite. No server runtime, no RSC tree, no framework router. Server-rendered React (App Router, server actions, SSR/SSG) is not this skill — that is `../nextjs/SKILL.md`.
@@ -8,9 +8,9 @@ origin: risco
8
8
 
9
9
  # React Native (app code)
10
10
 
11
- **Hand-off — the app vs its motion.** Animation and gestures in a React Native app — Reanimated
12
- worklets on the UI runtime, gesture handoff, sheets, press feedback, haptics — are
13
- `../motion-craft/SKILL.md`'s, whose guidance is platform-agnostic and applies to bare React Native as well as Expo.
11
+ **Scope.** App code, including animation and gestures (Reanimated worklets on the UI runtime, gesture
12
+ handoff, sheets, press feedback, haptics), in bare React Native and in Expo. Visual design of web
13
+ pages and screens is `../design/SKILL.md`'s.
14
14
 
15
15
 
16
16
  You write the code that runs *inside* the app: render, navigate, animate, list, persist, and author native modules. Mobile is split across two skills by **verb** — run the verb test before doing anything:
@@ -0,0 +1,220 @@
1
+ ---
2
+ name: scroll-film
3
+ description: "Use when a page's hero is a pre-rendered camera flight the visitor scrubs by scrolling, Apple-style: AI clips chained frame to frame so seams never pop, encoded for seeking, mounted with an engine that holds up on phones. NOT scroll reveals or pinning (that is `design`), NOT a finished MP4 (that is `ai-media`)."
4
+ tags: [scroll-scrub, scroll-video, scrollytelling, landing-hero, image-to-video, camera-flight, seamless-video]
5
+ recommends: [ai-media, fal, replicate-images, landing-copy, design, performance]
6
+ profiles: [full]
7
+ origin: risco
8
+ ---
9
+
10
+ # scroll-film — the camera moves, the scroll is the playhead
11
+
12
+ The page plays nothing. It holds a chain of pre-rendered clips and maps the scroll position to
13
+ their playhead, so the visitor flies through scenes — outside a building, inside it, on to the
14
+ next — without a cut. Three parts must be right, and each fails in its own way: the **chain**
15
+ (seams), the **encode** (seeking) and the **page** (phones). This skill owns all three.
16
+
17
+ **Not this skill — delegate:**
18
+ - Scroll-linked reveals, parallax, pinned sections, a horizontal pan, with no video → `../design/SKILL.md`.
19
+ - A finished MP4 with voice and music → `../ai-media/SKILL.md`.
20
+ - The call to the model endpoint (queue, webhooks, cost per call) → `../fal/SKILL.md` or `../replicate/SKILL.md`.
21
+ - The scene stills themselves → `../replicate-images/SKILL.md`.
22
+ - The words over the film → `../landing-copy/SKILL.md`. A video rendered from code → `../remotion-video/SKILL.md`.
23
+
24
+ ## 1. The gate: nothing renders before the user agrees to a number
25
+
26
+ A chain is many clips, each one slow and billed. Interview, price it, then wait for a yes.
27
+
28
+ 1. **Subject.** Ask it as an open question: what the world is about, in a sentence. A menu of
29
+ industries decides their business for them.
30
+ 2. **Scenes.** 4 to 7, in the order the camera visits them, proposed from their own value chain and
31
+ edited by them. The last one carries the call to action.
32
+ 3. **Look.** One art direction, written once as a style preamble and pasted unchanged into every
33
+ still prompt. Identical text is what makes separate renders read as one world.
34
+ 4. **Camera feel.** Always ask. It picks the chain shape (section 3).
35
+ 5. **Phones.** Always ask. A real phone version is a second chain (section 6) and doubles the video bill.
36
+ 6. **Brand.** 4 to 6 hex colours, a name, a tone. The page background is the scene background.
37
+
38
+ Then the bill:
39
+
40
+ | | Forward take (A) | Dive and hop (B) |
41
+ | --- | --- | --- |
42
+ | Video clips, desktop only | N | 2N − 1 |
43
+ | Video clips, with the phone chain | 2N | 4N − 2 |
44
+
45
+ Estimate = clips × seconds per clip × today's price per second, plus about 20% for re-rolls, plus N
46
+ stills. Read the price from the provider at build time; it moves (reading cost per call →
47
+ `../fal/SKILL.md`). State the total against the balance and wait for the yes. When money is tight,
48
+ offer a previz pass: the whole chain on the **same** model at its lowest resolution, approve the
49
+ journey, then render the finals.
50
+
51
+ ## 2. The model must continue a shot, not just start one
52
+
53
+ A seam holds only if the model starts a clip on an exact frame you hand it. That capability, not
54
+ quality or price, qualifies a model.
55
+
56
+ - Every clip needs a **start-frame** input. Hops (shape B) also need an **end-frame** input. Field
57
+ names vary by endpoint (`image_url`, `start_image`, `first_frame`; `end_image_url`,
58
+ `tail_image_url`, `last_frame`): read the input schema before batching. Model landscape →
59
+ `../ai-media/SKILL.md`.
60
+ - A **reference-image** input does not qualify. It steers the style; frame 0 is still a new
61
+ render, and the seam pops.
62
+ - **Probe once before the batch**, with one cheap clip from a real still prepared at the clip's
63
+ aspect (16:9, or 9:16 for phones). Frame 0 must match the still and the bill must match the
64
+ advertised price. For hop duty, add an end frame from another still: the last frame must land on
65
+ that composition. A near miss is normal; the engine's dissolve covers it. Commands →
66
+ `references/chain.md`.
67
+ - **One model for the whole chain.** Each model has its own grain, colour and motion; a swap
68
+ mid-chain keeps positions continuous and still pops. The one exception is a single clip a content
69
+ filter keeps blocking (`references/chain.md`).
70
+
71
+ ## 3. Pick the chain shape from the camera feel
72
+
73
+ | The user says | Shape | Clip k starts on | Clip k ends on | Fits |
74
+ | --- | --- | --- | --- | --- |
75
+ | "one continuous walkthrough", "one fixed angle gliding over it" | **A — forward take** | leg k−1's real last frame (leg 0: the first still) | nothing — no end frame | photoreal, interiors, product, real estate |
76
+ | "fly from one island to the next" | **B — dive and hop** | dive: its still. hop: dive k's real last frame | hop: dive k+1's real first frame | miniature, diorama, map-like worlds |
77
+
78
+ - A renders one leg at a time, because each needs the previous one's last frame. B renders every
79
+ dive in parallel, then every hop.
80
+ - Never give an A leg a wide establishing shot as its end frame. The camera pulls back to meet it,
81
+ and the take reverses.
82
+ - **The velocity rule.** Inside one clip the camera is free: orbit, rise, push in and ease back.
83
+ Across a seam it never reverses. Position matches, the motion flips, and it reads as a rewind,
84
+ in both scroll directions. So every leg ends by settling into a slow forward drift, and the next
85
+ one begins inside it. B reverses at every seam on purpose (pull out, hop, dive): charming over a
86
+ miniature map, a stutter in a realistic walkthrough. If the user picks B for a photoreal look, say
87
+ so before rendering.
88
+
89
+ Prompt templates (still, leg, dive, hop), the camera-move table by concept, the fixed-angle clause
90
+ and the batch loops → `references/chain.md`.
91
+
92
+ ## 4. The seam law: hand off pixels, not descriptions
93
+
94
+ Every render redraws its scene a little differently. A hop that ends on the *original still* of
95
+ scene 3 meets a dive that starts on *its own render* of scene 3: two pictures of one place, and the
96
+ seam pops. So both ends of every clip come from the neighbours' actual renders.
97
+
98
+ ```bash
99
+ # The true last frame: -update 1 overwrites per decoded frame, so the final one is what stays.
100
+ ffmpeg -v error -y -sseof -1 -i leg_02.mp4 -update 1 -q:v 2 leg_02_last.png
101
+ ffmpeg -v error -y -i dive_03.mp4 -frames:v 1 -q:v 2 dive_03_first.png
102
+ ```
103
+
104
+ A plain `-sseof -0.15 -frames:v 1` stops a few frames short of the end: 15 dB PSNR from the real
105
+ last frame on a 24 fps test clip. The engine parks the outgoing clip on its real last frame, so a
106
+ handoff from the earlier one starts the seam already off.
107
+
108
+ - Look at each last frame before chaining the next clip. In A, one bad handoff poisons every leg
109
+ after it: re-roll that leg.
110
+ - The engine dissolves over a tenth of a viewport at each seam. That hides an end frame that landed
111
+ close. It cannot hide a different picture.
112
+
113
+ ## 5. Encode for seeking, not for playback
114
+
115
+ Scrubbing seeks on every frame, and each seek decodes every frame since the last keyframe. The
116
+ keyframe interval sets how smooth the scrub feels.
117
+
118
+ ```bash
119
+ # Desktop: native resolution (never upscale), a keyframe every 8 frames, no audio, index first.
120
+ ffmpeg -i leg_01.mp4 -an -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \
121
+ -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart assets/vid/shot-01.mp4
122
+ # Phone: 720 wide, a keyframe every 4 frames; a phone decoder pays more per frame.
123
+ ffmpeg -i leg_01_portrait.mp4 -an -vf scale=720:-2 -c:v libx264 -preset slow -crf 23 -pix_fmt yuv420p \
124
+ -g 4 -keyint_min 4 -sc_threshold 0 -movflags +faststart assets/vid/shot-01-m.mp4
125
+ # Poster: the clip's first frame. It is the LCP image and the reduced-motion fallback.
126
+ ffmpeg -v error -i assets/vid/shot-01.mp4 -frames:v 1 -c:v libwebp -quality 82 assets/shot-01.webp
127
+ ```
128
+
129
+ One recipe for every clip in the chain. All-intra (`-g 1`) is the last resort for a low-end phone
130
+ that still stutters, at about three times the size.
131
+
132
+ ## 6. Phones get their own chain, not a crop
133
+
134
+ A 16:9 film on a portrait phone shows its middle quarter. When the user asked for phones, render
135
+ the chain again at 9:16, frame-locked to its own renders. A landscape frame inside the portrait
136
+ chain pops on both sides. Start images: each still composited onto a 1080×1920 canvas in the page
137
+ background colour. The encodes are the `-m.mp4` files above, and each portrait clip's first frame is
138
+ its `posterMobile`.
139
+
140
+ No budget for it: the engine crops the landscape film with `object-fit: cover`, steered by
141
+ `--film-focus`. Tell the user this is a stopgap. Never ship it as "the mobile version".
142
+
143
+ ## 7. Mount the page
144
+
145
+ Copy `assets/scrub-engine.js` and `assets/page-template.html` into the project. The template is a
146
+ working page: its copy is real HTML that reads without JavaScript, and the script moves it onto the
147
+ film once the engine mounts.
148
+
149
+ ```js
150
+ const film = ScrollFilm.mount(document.getElementById('film'), {
151
+ shots: [
152
+ { section: 0, src: 'assets/vid/shot-01.mp4', srcMobile: 'assets/vid/shot-01-m.mp4',
153
+ poster: 'assets/shot-01.webp', posterMobile: 'assets/shot-01-m.webp', length: 1.4, hold: 0.4 },
154
+ { section: 0, src: 'assets/vid/hop-01.mp4', poster: 'assets/hop-01.webp', length: 0.9 },
155
+ // one entry per clip, in chain order
156
+ ],
157
+ onChange: ({ section, sectionProgress }) => { /* fade the copy block for `section` */ },
158
+ });
159
+ ```
160
+
161
+ What the engine does, and the failure each part prevents:
162
+
163
+ - **Loads each clip as a Blob.** On a host that ignores byte-range requests, a streamed `<video>`
164
+ reports `seekable` as [0, 0] and every seek lands on frame 0. A Blob is always seekable.
165
+ - **Keeps one seek in flight per clip.** Queued seeks are what freeze a phone on a fast flick.
166
+ - **Keeps the poster up until a frame has painted**, and primes each clip with a muted play and
167
+ pause, again on the first touch. iOS paints nothing for a seeked video that never played.
168
+ - **Picks phone sources at mount**, ignores height-only resizes on touch (the URL bar sliding), and
169
+ re-lays out on rotation.
170
+ - **Reduced motion or Save-Data:** no video is fetched; posters dissolve with the scroll and the
171
+ copy still works.
172
+ - **Fetches a clip once**, when the scroll comes within 1.5 viewports of it.
173
+ - **`destroy()`** removes everything it added. Call it from the effect cleanup in React or Vue.
174
+
175
+ `design` bans raw scroll listeners for animation. This one is passive and only requests a frame. If
176
+ the page already owns a scroll source (ScrollTrigger, `useScroll`), pass `listen: false` and call
177
+ `film.update()` from it. All options, the state object, theming, pacing and the Next.js and Vue
178
+ wiring → `references/engine.md`.
179
+
180
+ The film layer is `aria-hidden`; the meaning lives in the copy. Its words → `../landing-copy/SKILL.md`.
181
+ Its type, colour and spacing → `../design/SKILL.md`.
182
+
183
+ ## 8. Verify before calling it done
184
+
185
+ 1. **The chain, by script.** Pass the clips in chain order:
186
+
187
+ ```bash
188
+ bash <skill-dir>/scripts/verify.sh --frames qa/seams \
189
+ assets/vid/shot-01.mp4 assets/vid/hop-01.mp4 assets/vid/shot-02.mp4
190
+ bash <skill-dir>/scripts/verify.sh --gop 4 --portrait \
191
+ assets/vid/shot-01-m.mp4 assets/vid/hop-01-m.mp4 assets/vid/shot-02-m.mp4
192
+ ```
193
+
194
+ It checks each clip's encode (H.264, yuv420p, no audio, index first, keyframe interval), that
195
+ the chain shares one size and frame rate, and scores every seam against the clips' own
196
+ frame-to-frame step. A fixed SSIM threshold cannot tell: on a soft landscape photo, a cut to a
197
+ different picture still scored 0.76 and a fresh render of the same scene 0.92. Measured against
198
+ the step (0.98 there), the frame-locked seam passed at 0.987 and both others were flagged. Open
199
+ every flagged pair in `qa/seams/` and judge by what you see: a wrong seam shows different objects.
200
+ 2. **In a browser.** Scroll slowly across each seam on desktop. Then emulate a phone at 4 to 6× CPU
201
+ throttle and flick fast. Turn reduced motion on: posters only. In the Network panel, the phone
202
+ gets the `-m.mp4` files and the desktop gets the masters.
203
+ 3. **On a real iPhone**, if phones shipped: the first scene shows at once and no scene goes blank.
204
+
205
+ ## Anti-patterns
206
+
207
+ | Failure mode | Usually | Fix |
208
+ | --- | --- | --- |
209
+ | A visible pop at one seam | The hop's end frame was the original still, or came from `-sseof` a few frames early | Extract the true frames from the neighbouring renders (section 4) |
210
+ | The camera seems to rewind at every seam | Velocity reverses across the seam: B in a realistic walkthrough, or an A leg given a wide end frame | Shape A with the settle-into-drift clauses and no end frame |
211
+ | Stuck on the first frame in production, fine locally | The host ignores byte ranges | Load as Blob; keep it when porting the engine to a framework |
212
+ | A phone freezes on a fast flick | A 1080p, 8-frame-GOP master on a phone decoder, plus queued seeks | `-m.mp4` encodes at GOP 4; keep one seek in flight |
213
+ | Black scenes on iPhone until the first touch | The poster was hidden on `loadedmetadata`, or `muted`/`playsinline` lost in a port | Keep the poster until a frame paints; keep both attributes |
214
+ | The film scrolls away instead of sticking | An ancestor has `overflow: hidden`, often `body { overflow-x: hidden }` | `overflow-x: clip` |
215
+ | A cheaper model "just for the hops" | Each model renders its own grain and motion | One model for the chain; previz on the same model at low resolution |
216
+ | A content filter blocks a harmless interior | Words like bed, pool, spa or wine in the prompt | Re-roll, neutral wording, then the remedies in `references/chain.md` |
217
+
218
+ ## Orientación (siempre)
219
+
220
+ Habla con la voz de `orient`: frases cortas, una idea por frase, y cada respuesta se entiende sola. Registro técnico o con analogías según `technical_level` en `02-DOCS/wiki/harness/user-profile.md`. Cierra cada turno con el **bloque-brújula** (📍 dónde estás · ➡️ siguiente, terminando en pregunta; ✅ y 🧭 cuando hay algo hecho o decidido). **Nunca termines en seco.** Protocolo completo: skill `orient` → `skills/orient/references/orientation-contract.md`. (Defiere a `suggest` el "¿instalo la skill que falta?".)