# Motion Web

> Design and build motion-first creative websites — page design (structure, type scale, palette, spacing) plus interaction choreography: scroll animation, Framer Motion, GSAP, and light 3D via Three.js/R3F. Use for animated landing pages, creative portfolios, product showcases, WebGL-lite scenes, and motion audits. Also use when tuning interaction handfeel (手感差/太生硬/不跟手), replicating motion from a video or URL reference (复刻), building 3D depth from flat image assets (伪3D/抠图), diagnosing dead scroll-timeline stretches, mixing scene audio, auditing discoverability (点不开/没人会发现), building headless verification probes, or fixing a page that moves well but looks generated (不好看/像AI做的/没设计感). Equally for building a whole site from nothing (从0到1做个网页/落地页/官网/作品集): stack choice, sections and copy, components (导航/hero/表单/卡片/定价表), and production polish (favicon/OG/404/空状态/暗色模式). Not for dashboards, admin UI, or full 3D scene generation.

- Skill: `feitangyuan/motion-web` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add feitangyuan/motion-web`
- Raw SKILL.md: https://api.skillmd.com/api/skills/feitangyuan/motion-web/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: feitangyuan (https://skillmd.com/u/feitangyuan)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/feitangyuan/motion-web

---


# motion-web

Motion-first creative web. The skill handles all phases: reference → concept → spec → build → audit.

**This skill builds whole web pages.** Motion is what makes them good; it is not the whole job. A
request with no motion in it at all — "做个落地页", "帮我把这个官网做出来" — is still this skill, and the
deliverable is a finished site: stack, tokens, sections, copy, components, states, meta, deploy.

**Priority order — 1 and 2 are co-equal:**
1. Motion concept and interaction choreography
2. **Page design** — structure, type scale, palette, spacing rhythm, the thing it looks like before anything moves
3. **The page as a built artifact** — sections and their copy, the component vocabulary, the token layer,
   the states off the happy path (`page-blueprints.md`, `components.md`, `project-setup.md`, `production-polish.md`)
4. Light 3D or canvas only when it strengthens the page
5. Performance, accessibility, reduced-motion fallbacks

**A great mechanic on a default-looking page is a failed page.** Motion is the *advantage*, not the
excuse. The most common failure of this skill is shipping one clever interaction glued onto a layout
nobody designed — if the page would embarrass you with the motion switched off, it is not done, and
no amount of spring tuning fixes it. Design is not step 2 in time; it is a second thing that must also
be true at the end. → `references/page-design.md`, `references/design-slop.md`

This *is* a design-and-build skill for one class of site: creative and marketing web — landing pages,
product pages, portfolios, studio sites, campaigns, editorial, light docs. It builds them end to end.
It is **not** a dashboard / admin / data-dense app-UI skill, and not a full 3D-scene generator
(those route to `3d-scene-studio`). A creative page containing app-shaped fragments — a pricing table, a
sign-in, a console screenshot — is in scope; a logged-in product interface is not.

---

## Agentic Intent Detection (CRITICAL — Read This First)

**The user will often say very little. Detect their intent from minimal input and act autonomously.**

### Signal → Intent Map

| What the user sends | Detected intent | What to do |
|---|---|---|
| "做个网页" / "做个落地页/官网/作品集" with a topic, no reference | `full-build` | **The whole site, not a mechanic.** [page-blueprints.md](./references/page-blueprints.md) → [project-setup.md](./references/project-setup.md) → [page-design.md](./references/page-design.md) → build → [components.md](./references/components.md) → [production-polish.md](./references/production-polish.md) |
| "这页要放什么" / "该有哪些 section" / "信息架构" / "文案怎么写" | `blueprint` | [page-blueprints.md](./references/page-blueprints.md) — pick a spine, write real copy, mark every invented fact |
| "导航" / "按钮" / "表单" / "卡片" / "footer" / "FAQ" / "定价表" / "弹窗" / "组件" | `component` | [components.md](./references/components.md) — the dialect table first, then the state matrix. A component without its states is not done |
| "用什么技术栈" / "Astro 还是 Next" / "怎么起项目" / "design token" / "字体" / "部署" | `setup` | [project-setup.md](./references/project-setup.md) — page ladder ≠ motion ladder; start at rung 0 and climb |
| "像 demo 不像成品" / "OG 预览是空的" / "没有 favicon" / "404" / "空状态" / "暗色模式" / "表单没校验" | `polish` | [production-polish.md](./references/production-polish.md) — all of it is checkable; run §9 rather than estimating |
| A bare URL (no other text) | `url-drop` | Open it and deconstruct it (Replicate Mode steps 1–2), then ask: rebuild this, or use it as the reference for something of theirs? |
| A URL + "做个网页" or "参考这个" | `replicate → build` | Deconstruct the URL first (Replicate Mode), then propose a motion concept for the user's own content |
| 参考 + "复刻出来并收录成 case" / "存进 skill" / "做成案例" | `case-authoring` | [cases/AUTHORING.md](./cases/AUTHORING.md) — Phase 0 gate first: strip the images out of the reference; if nothing survives, settle the assets **before** building, not after |
| A URL + "复刻" / "做一个类似的" / "参考这个做" | `replicate` | **Deconstruct → map to patterns → build immediately.** |
| A URL to a paper/article/blog | `doc-drop` | Read it, name the techniques with extractable values, build from one if asked (Document / Article Drop below) |
| A vague brief ("做个很酷的网页" / "做个 portfolio") | `freeform-build` | Orchestrate via [composition-guide.md](./references/composition-guide.md) → choose style+arc independently |
| A topic only ("做一个关于 AI 的落地页") | `spec → build` | Orchestrate via [composition-guide.md](./references/composition-guide.md) → propose visual direction, build immediately |
| A detailed brief with references | `spec → build` | Write MOTION_SPEC.md → get approval → build |
| "审查" / "有什么问题" / "gap" | `audit` | Inspect existing work for motion/perf/accessibility gaps — **including the still frame**, per [design-slop.md](./references/design-slop.md) |
| "不好看" / "太丑" / "像 AI 做的" / "没设计感" / "普通" | `design-audit` | Screenshot with motion **off** and run the gates in [design-slop.md](./references/design-slop.md). Do not tune springs — the complaint is about the still frame |
| "版式" / "排版" / "字太小" / "留白" / "结构" / "section 都长一样" | `design` | [page-design.md](./references/page-design.md) — macrostructure, type scale, spacing rhythm, measured against current award winners |
| "加上…" on existing page | `incremental-build` | Extend current implementation with new motion |
| A **video** reference (Pinterest/Dribbble/X clip) | `video-ref` | **STOP — run the Video Reference Protocol below before writing any code** |
| "手感差" / "太生硬" / "不跟手" / "四平八稳" on existing motion | `handfeel-tune` | Audit against [handfeel.md](./references/handfeel.md) — springs, velocity coupling, secondary motion |
| "跟手但总差一点" / "一抖一抖" / 手感随帧率变 on anything that **follows** (camera, cursor, rail, tooltip) | `handfeel-tune` | Not §1's spring — [handfeel.md](./references/handfeel.md) §7: `1-exp(-k·dt)`, the `speed/k` lead term, and never correcting after the filter |
| "能不能不用 GSAP" / 只是个入场或视差 / 想要更轻 | `build` | Check rung **1.5** first — native `animation-timeline` / `animation-trigger` / `sibling-index()` → [build-mode.md](./references/build-mode.md) ladder + [pattern-recipes.md](./references/pattern-recipes.md) #16. Firefox gets the static end-state |
| 素材是一个**真实存在的地方**（实拍/扫描），要走进去 | `build` | Rung **5.5** — 3D Gaussian splats before models → [build-mode.md](./references/build-mode.md) ladder |
| 卡片/图片要**弯折**、旗帜、翻页、把截图钉进立体面、"skew 不够用" | `warp` | Corner-pin homography → [pattern-recipes.md](./references/pattern-recipes.md) #17. **Guard every quad and clamp edge tilt to `asin(h/w)`** — an unguarded quad paints a screen-sized block with no error |
| "点不开" / "只有一个能点" / "没人会发现" / "看不到" on existing motion | `affordance-audit` | **Do not touch hit-test code first.** Audit against [affordance.md](./references/affordance.md) — the mechanism is usually fine; the invitation is missing or expired |
| A pile of **image assets** + "做成一个 3D 场景" / "要有纵深" / "camera 要能走进去" | `image-plane-3d` | **Do not reach for models.** Build the scene from flat planes in a real perspective camera → [image-plane-3d.md](./references/image-plane-3d.md) (ladder rung 5) |
| "有拼接感" / "没融进去" / "像贴上去的" on a rendered scene | `image-plane-3d` | Measure before touching art: mean `R−B` and luminance per asset batch, contact shadows present → [image-plane-3d.md](./references/image-plane-3d.md) §6–7 |
| "这个该用什么画" / "DOM 还是 canvas" / 页面只会输出 div 和系统字体 / 参考明显不是 DOM 能做的 | `render-layer` | **先定层再定机制** → [render-layer.md](./references/render-layer.md) §1。层是一个声明，写进 case README，由 `verify_case.py --layer` 核对 |
| 手绘 / 墨线 / 描边风的 3D，"像画出来的不像渲出来的"，boil / 抖线 / 纸纹 | `ink-render` | [render-layer.md](./references/render-layer.md) §3 — 深度不连续描边（法线折痕关掉）、静态 warp 与动态 boil 分离、boil 时钟 11Hz 对 60fps 渲染 |
| Raw plates / renders that still need cutting out, "抠图", "素材怎么进场景", "图糊了" | `asset-pipeline` | Key against the image's **own** background, crop to the alpha bbox, script the metadata, print the per-element resolution budget → [image-asset-pipeline.md](./references/image-asset-pipeline.md) |
| Any headless check; a number disagrees with what you see; "怎么验" | `verify` | Build the probe surface first — pinnable clocks, `__seek`, `__hold`, state exits — then measure → [verification-harness.md](./references/verification-harness.md) |
| "这一段什么都没发生" / "节奏不好" / "一堆字一下就出来了" on a scroll timeline | `timeline-audit` | Lay every track against one scroll axis; dead beats are coinciding plateaus, not one slow layer → [timeline-orchestration.md](./references/timeline-orchestration.md) |
| Long scroll scene with many assets; "卡", "加载慢", "内存爆了", hundreds of plates | `scene-streaming` | Activate by screen window, slice big uploads, yield inside the scan, dispose completely → [scene-streaming.md](./references/scene-streaming.md) |
| Anything with sound — BGM, ambience, interaction SFX, "音效太少", "氛围音听不清但也别吵" | `scene-audio` | Three buses (bgm / hand / world), per-act curves, discrete events over a louder bed → [scene-audio.md](./references/scene-audio.md) |

**Never ask the user what mode to use.** Detect it. Proceed. Only stop if the brief is truly ambiguous between two very different outputs (e.g., spec vs. immediately building).

---

## Video Reference Protocol (硬规则 — 不许猜)

**Trigger: the reference is a video you cannot actually watch** — Pinterest pin, Dribbble shot, X clip, 站外短链. A title or text description is NOT motion information.

0. **Read the thread before you conclude there is no live page.** The author almost always posts the
   URL in a reply, and the replies also tell you whether the piece is even a web page. Measured
   2026-09-02: a previous session declared seven video references URL-less and spent its Phase 1 on
   frame measurement; **all seven had live sites, posted by the authors themselves**, and three of
   four clips in one post turned out to be Jitter / Figma Motion / Rive renders with no web
   implementation at all (`cases/AUTHORING.md` Phase 1). `safe-x tweet <url> --json`, then resolve
   every `t.co` link in the replies.
1. **Never build motion from a guess.** If all you extracted is a title/description, you know the *topic*, not the *motion*. Building anyway burns a full iteration — this is a documented failure mode (2026-07-06, seafood hero: guessed "orbit", actual was a rising diagonal; two rebuilds).
2. **Ask for 2–3 keyframes immediately.** One sentence: "这个视频我打不开，截 2-3 帧关键画面（入场/中间/离场）发我"。
3. **State the trajectory as explicit geometry before coding**, and get confirmation:
   - Anchor points: where the element enters / crests / exits (corners, thirds)
   - Attitude: rotation follows path tangent / stays constant / lags behind
   - Secondary reactions: what else moves (background shapes, lines, text) and how
   - Example: "右下入场 → 中心 → 左上离场的上升对角线，鱼头恒定朝左上" — wrong guesses die here, not in code
4. Only then map to pattern-recipes / handfeel.md and build.

A single screenshot follows the same rule: one frame gives pose, not trajectory. If the *path* matters, ask for more frames.

---

## Replicate Mode (复刻模式)

When the user sends a URL with "复刻" / "做一个类似的" / "能不能做这种效果" — deconstruct it, then build. There is no storage step; the deliverable is the page.

### Replicate Execution Path

1. **Open the page and read it as three layers**, with whatever browser tooling is available (Claude
   Preview, `browser-use`, Playwright): the **stack** (library, easing, keyframes, CSS variables — the
   bundle names them), the **scroll choreography** (which element is pinned over which band, rails and
   their px distance, parallax rates, scrubbed properties, entrance points — sample ~9 scroll positions
   and diff `getBoundingClientRect` plus computed styles between them), and the **WebGL layer** (program
   count, draw calls, custom uniform names; wrap `getContext` before page scripts run if you need the
   shader sources). Two scripts cover the measurable halves: `scripts/measure_structure.py <url>` for
   the static page (sections, nav, buttons, token scales, media, meta, mobile overflow) and
   `scripts/measure_churn.py <url>` for how much of the page actually moves under a real wheel.
   **The URL must be the work itself**, never a gallery listing (`awwwards.com/sites/<slug>`): a
   listing page hands you the gallery's own chrome, and six different winners come back with the same
   library, the same keyframes and the same font.
2. **Deconstruct** — state what you found, and quote the numbers rather than impressions:
   - "GSAP 3.12 + ScrollTrigger + Lenis；`.home-animate__wrapper` 在 0.25–0.50 被 pin 住，
     期间 `.icon` 横移 +1545px，出场是 0.25/0.38/0.62 三段 stagger"
   - "背景是原生 WebGL2，14 个 program、944 draw calls，uniform 里有 bloomStrength/blurTexture1-5
     和 cloudCoverage/cloudDensity —— 是 UnrealBloom + 体积云"

   **Never report "用了 WebGL" off a context alone** — an unsized 300×150 context with no linked
   program is Modernizr-style capability detection, not a scene. Count linked programs and draw calls.
3. **Map to pattern-recipes** — find the closest recipe in `references/pattern-recipes.md`
4. **Fill the gap** — what the reference uses that we don't have in recipes, write from scratch based on extracted values
5. **Build immediately** — scaffold the page, run it, verify visually
6. **Report** — "复刻了 [site] 的核心动效，用了 [X] 实现 [Y]，和原版差异是 [Z]"

### Replicate ≠ Copy

- **Do**: Recreate the interaction pattern, motion character, timing feel
- **Don't**: Copy proprietary brand colors, exact copy text, identifiable product UI
- **Do**: Adapt the technique to the user's own content and palette
- Note in the report what was adapted vs. what was faithful to the original

---

## Freeform Build Mode (When User Gives Zero Direction)

When the user says "做个网页" / "随便做" / "自己想一个主题" or gives a simple vague topic (e.g. "做一个AI agent平台落地页"), this is an invitation to be creative.
**Do NOT default to a generic template, do NOT use rigid 1-to-1 routing mapping, and do NOT ask clarification questions.**

### Freeform Action Path (Runtime Creative Loop — 创造发生在执行中，不在文档里)

The user should NEVER need to supply a reference. But novelty also does NOT come
from reading these docs — docs are constraints and physics, not answers.
Novelty comes from **fresh inputs sampled at runtime + a build-test-kill loop
where you are your own critic**. Execute:

1. **Sample the present** (runtime input, not memory): open this month's
   Awwwards SOTD / Godly / SiteInspire listings and look at 3–5 of the winning
   pages themselves — the work, never the gallery page. You're not copying;
   you're locating the current frontier so you can land past it. 2 minutes.
   Look at **structure and type scale**, not only the effect — that is the half
   that decides whether the page reads as current (`page-design.md` §1).
2. **Invent 3 candidate mechanics** from the topic's own physics via the
   generative operators (composition-guide §5). Kill any candidate that
   resembles the graveyard OR anything you just saw in step 1.
3. **Sketch before committing**: build the signature interaction as a minimal
   single-file prototype (one div, one rAF loop, fake content, <15 min).
   Not the page — just the mechanic.
4. **Test it on yourself**: run it and drive it with the currently available
   browser tooling — Claude Preview (`preview_start` → `preview_eval`/`preview_click` →
   `preview_screenshot`) first, `browser-use` as fallback — mouse sweeps, wheel
   flicks, screenshots at extremes, then look at the evidence. Ask:
   会不会玩 10 秒就腻? 截两帧给人看,能不能猜出交互规则? If it fails — mutate the
   operator or pick the next candidate. Do not ask the user to be your eyes.
5. **Design the page — as a separate act, before the mechanic goes into it.**
   Not "pick a palette and start coding". Three outputs, in order:
   (a) the **blueprint and the real copy** → `references/page-blueprints.md` — which
   sections exist and what they actually say; (b) the **macrostructure, type scale
   and spacing rhythm** → `references/page-design.md`, with concrete values in
   `data/visual-bank.json`; (c) the **token layer and scales** →
   `references/project-setup.md` §2–§5. The mechanic then gets placed *into* a page
   that already works, which is the opposite order from the one that produces
   「一个破动效贴在没设计的页面上」.
6. **Build it**, components and their state matrices from `references/components.md`,
   springs from `references/motion-tokens.md`, full-page linkage per handfeel.md.
7. **Design gate — run this before you show anything.** Screenshot the page with
   **all motion disabled** (`prefers-reduced-motion`, or comment out the mechanic)
   and judge that still frame on its own:
   - Would this still frame place on Awwwards with zero animation? If the honest
     answer is "no, it's a centred headline and three cards", the design is not done.
   - Run the slop gates in `references/design-slop.md`. Any hit = revise, not ship.
   - Only then ask the motion questions: 会不会玩 10 秒就腻? 截两帧能不能猜出交互规则?
   A page that passes step 4 and fails this one is the documented failure mode this
   loop exists to prevent — step 4 only ever tested the mechanic.
8. **Finish it**: `references/production-polish.md` — head, OG, favicon, 404, the
   states off the happy path, and the ten manual checks in §9. A page that stops
   before this is a demo, not a deliverable.
9. **Bury the mechanic**: append one line to the graveyard (composition-guide)
   so it can never be reused — the loop stays forced-fresh.

Pitch to the user only if two surviving candidates are genuinely different
directions; otherwise just build the best one and explain the metaphor after.

---

## Document / Article Drop

If the user drops a paper, article or design blog: read it, name the 2–3 techniques in it that carry
extractable values (library, easing, timing, a shader idea, a layout rule), and ask one question —
build from one of them now, or keep going with the brief? If they say build, the most distinctive
pattern becomes the page's primary motion concept and goes through the same Build Order as everything
else. Nothing is stored; a technique that mattered goes into `pattern-recipes.md` after it has shipped.

---

## Modes (for explicit user direction)

- `spec`: turn a brief + references into a buildable `MOTION_SPEC.md`
- `build`: implement an approved motion-first web experience
- `audit`: inspect an existing motion-heavy page or implementation for gaps
- `design-audit`: the complaint is about how it *looks*, not how it moves — judge the still frame first
- `replicate`: deconstruct a reference page in numbers, then rebuild the pattern on the user's content
- `freeform-build`: user gives no direction → make creative choices independently
- `full-build`: build a whole site 0→1 — blueprint + copy, stack + tokens, design, components, motion, polish
- `blueprint`: decide what sections a page needs and write their copy
- `component`: design/build a specific component with its full state matrix
- `setup`: pick the page stack, scaffold the project, lay the token/type/font layer
- `polish`: the finishing pass — meta, OG, 404, off-happy-path states, dark mode, pre-ship checks

---

## Build Order (Always Follow This)

0. **Pick the page stack** → `references/project-setup.md` §1 — one HTML file / Vite / Astro / Next. This is a
   *different ladder* from the motion one; do not let either pick the other. Start at rung 0.
0.5 Pick the motion stack rung → `references/build-mode.md` **Tech Stack Decision Ladder** — lowest rung that reaches the goal; never default to R3F because the reference "looks 3D", and never default to *models* because the scene needs depth (rung 5 builds the whole scene out of images)
1. **Pick the blueprint and write the copy** → `references/page-blueprints.md` — which sections exist, in
   what order, and the real words in them. Layout is a response to real text; placeholder copy means the
   page has not been designed. Then pick arc type → `references/choreography-arc.md`
2. **Design the page** → `references/page-design.md` — macrostructure, type scale, palette, spacing
   rhythm. Concrete values in `data/visual-bank.json`. This is a real step with its own output, not a
   line item: decide what the page looks like **with the motion switched off**, and be willing to
   defend that still frame on its own
2.5 **Lay the token layer and the scales** → `references/project-setup.md` §2–§5 — three-level tokens,
   fluid type scale with a real jump, space scale, fonts with metric overrides, `@layer` order. Doing this
   after components exist means editing every component.
3. Pick motion tokens → `references/motion-tokens.md` (duration, easing, spring)
3.5 **Build the component vocabulary** → `references/components.md` — pick the dialect (creative vs product,
   the two are measurably different systems), then give every interactive component its full state matrix.
   A missing `:focus-visible`, error or empty state is the most common "像 demo" cause.
4. Check pattern library → `references/pattern-recipes.md` for reusable implementations
5. **If the scene is built from image assets, run the asset pipeline before the runtime** → `references/image-asset-pipeline.md`. Cutouts, scripted metadata, per-element resolution budget. No amount of shader work fixes an asset that was keyed against the wrong background or is being enlarged 0.83×. Decide the **topology** at the same time (camera moves vs world moves → `image-plane-3d.md` §12) and, if it is long, the **streaming policy** → `references/scene-streaming.md`
6. Implement per → `references/build-mode.md`, **building the probe surface as you go** → `references/verification-harness.md` §1 (retrofitting `__seek` / `__hold` / pinnable clocks costs more than building them)
7. For a multi-track scroll timeline, lay the tracks out horizontally → `references/timeline-orchestration.md`; if the page has sound → `references/scene-audio.md`
8. For anything interactive that isn't an obvious button: mark the invitation per → `references/affordance.md`
9. **Run the design gate before the motion gate** → `references/design-slop.md`. Screenshot with all
   motion disabled and judge that frame first; a page that only works while moving has not been designed
10. **Finish it** → `references/production-polish.md` — head/OG/favicon, 404, the states off the happy path,
    dark-mode decision, and the ten manual checks in §9. Skipping this is what makes a good page read as a demo
11. Verify desktop + mobile + reduced-motion + **the affordance checklist and its numeric oracles**

**Mandatory pre-flight — not optional, not a suggestion:** before writing a single line of GSAP, Framer Motion, or Three.js/R3F code, open the matching framework section in `references/build-mode.md` (Framework-Specific Rules) and the `Failure Patterns to Avoid` table. Do this even if you're confident you remember the pattern — the table exists specifically because remembered patterns silently regress (e.g. GSAP `.from()` without `gsap.context()`/`.revert()` under React StrictMode). Skipping this step and reinventing a fix that's already documented there is a process failure, not a stylistic choice.

---

## Load Only What You Need

| Mode | Read these references |
|---|---|
| `spec` | motion-spec.md + baseline-ui.md + choreography-arc.md |
| **every build, without exception** | **page-design.md + design-slop.md** — the still frame is half the deliverable |
| a brief in a case's class, or you need its mechanism | the case's `README.md` first (it has the 可以照搬/不许照搬 split), then its `index.html` |
| **every 0→1 site build** | **page-blueprints.md + project-setup.md + components.md + production-polish.md** — sections & copy, stack & tokens, component states, the finish |
| `full-build` / "做个网页" with no reference | page-blueprints.md + project-setup.md + page-design.md + design-slop.md + composition-guide.md + components.md + production-polish.md |
| `blueprint` / "该有哪些 section" / 信息架构 / 文案 | page-blueprints.md + page-design.md §1 |
| `component` / 导航 / 表单 / 卡片 / footer / 弹窗 | components.md + baseline-ui.md + (affordance.md if it isn't an obvious button) |
| `setup` / 技术栈 / token / 字体 / 部署 | project-setup.md (+ build-mode.md ladder for the *motion* stack) |
| `polish` / 像 demo / OG / favicon / 404 / 空状态 / 暗色模式 | production-polish.md + baseline-ui.md |
| `build` | build-mode.md + motion-tokens.md + pattern-recipes.md + (light-3d.md if canvas/WebGL) |
| `image-plane-3d` / scene built from image assets / "有拼接感" | image-asset-pipeline.md + image-plane-3d.md + light-3d.md + verification-harness.md + (scene-streaming.md if it's long; affordance.md if anything in the scene is clickable) |
| `scene-streaming` / 长滚动大场景 / 卡 / 加载慢 | scene-streaming.md + image-asset-pipeline.md §6, §9 |
| `asset-pipeline` / 抠图 / 素材元数据 / 图糊了 | image-asset-pipeline.md (+ image-plane-3d.md §2 for how the runtime consumes it) |
| `verify` / building any oracle / a metric disagrees with the eye | verification-harness.md + affordance.md §9 |
| `timeline-audit` / scroll-driven多拍编排 / "这一段什么都没发生" | timeline-orchestration.md + choreography-arc.md |
| `scene-audio` / BGM / 环境音 / 交互音效 | scene-audio.md + verification-harness.md §3 |
| `freeform-build` | page-design.md + design-slop.md + composition-guide.md + choreography-arc.md + visual-bank.json + motion-tokens.md + pattern-recipes.md |
| `audit` | build-mode.md + motion-tokens.md |
| `handfeel-tune` / "不跟手" complaints | handfeel.md + motion-tokens.md |
| `affordance-audit` / "点不开" / "发现不了" complaints | affordance.md + handfeel.md |
| `video-ref` | (protocol above) then handfeel.md + pattern-recipes.md |
| `replicate` / `url-drop` | pattern-recipes.md + handfeel.md + page-design.md + design-slop.md |
| portfolio/personal site | page-blueprints.md (the portfolio blueprint) + components.md |

---

## Core Rules

- Motion is the main design material, not the final polish layer.
- **Anything that follows something else is not a spring problem.** Cameras, cursors, rails and tooltips
  must arrive and stop. Steady-state lag behind a moving target is `speed / k` — arithmetic, not a tuning
  failure — and a correction written after the filter gets undone next frame (`handfeel.md` §7).
- **「飘」 is a stillness ratio, not an easing curve.** Easing changes how fast the source is read; it never
  changes how often a new state appears. Measure the fraction of near-identical adjacent frames before
  touching a curve (`timeline-orchestration.md` §11).
- **Climb the ladder from rung 1.5, not rung 4.** A reveal, a parallax band, a stagger and a scroll-spy nav
  are now native CSS; author the finished state first and put the timeline behind `@supports`, because
  Firefox implements none of it (`build-mode.md` ladder, `pattern-recipes.md` #16).
- **Buildable ≠ findable.** An interaction is not done when it works; it is done when a first-time visitor
  discovers it without being told. Every interactive element that isn't an obvious button owes an answer to
  the three questions in `references/affordance.md` §1.
- **The test has to encode the user's complaint, not your model of it.** If the user says "I can't open it"
  and your check says "it opens", you are measuring a different thing — go find their question first.
- **Decide what number would prove it, then print that number.** Screenshots are expensive and settle
  nothing. Coverage sweep, handoff size error, safe-area containment, window-vs-dwell — all one headless
  run (`references/affordance.md` §9).
- **A general quality score is worse than no test.** Every oracle must encode one stated complaint; the
  aesthetic verdict stays with a human looking at the thing (`verification-harness.md` §0).
- **Assets before runtime.** In an image-built scene the metadata is a build artifact, never a hand-edit —
  if you are nudging a `baseline` to make something sit right, the bug is upstream (`image-asset-pipeline.md`).
- **A dead beat is several layers' flat sections landing together**, not one slow layer. Read a scroll
  schedule horizontally (`timeline-orchestration.md` §1).
- **The deliverable is a page, not a mechanic.** A brief with no motion in it is still this skill, and the
  job is a finished site — sections with real copy, a component vocabulary with its states, a token layer,
  meta and a 404. Handing back one beautiful interaction and a template around it is the failure this
  skill's whole design half exists to prevent.
- **The two component dialects are measurably different systems.** Creative pages: median **1** distinct
  radius, **0** shadowed elements, 37 % uppercase buttons, no nav CTA, a 2-column footer. Product pages:
  median **8** radii, ~18 shadowed elements, 1 % uppercase, 2 nav CTAs, a 5-column 35-link footer
  (n=32, `components.md` §1). Pick one and be consistent — building a creative page out of product
  components *is* the "像 AI 做的" complaint.
- **A component without its states is not built.** hover / `:focus-visible` / active / disabled / loading /
  error / empty / success. The missing state is almost always the one the user notices
  (`components.md` §2).
- **Copy before layout.** Layout is a response to real text length; a page laid out around placeholder
  copy has to be redone. If the user supplied no facts, write the draft and mark every invented slot
  `[…]` — never invent proof (`page-blueprints.md` §4, `design-slop.md` A8).
- **Award-winning is not well-built.** In the same 32-site run, 2 of 18 award sites had horizontal
  overflow at 390px, 5 had no `<h1>`, and only 37 % of their images reserved a box. Copy their design
  language; do not copy their hygiene (`production-polish.md`).
- **A projective transform has a legality condition, and violating it fails silently.** Corner-pin
  (`matrix3d`) only behaves while the destination quad stays convex — past that the homography flips
  through infinity and paints a screen-sized block with no console error. Guard the quad *and* clamp
  edge tilt to `asin(h/w)`; the guard alone is a visible snap (`pattern-recipes.md` #17).
- **From `cases/`: take the mechanism, never the skin.** The source ships in full because re-deriving a
  solver is waste — but palette, type values, grid, section order and copy belong to that case. A page
  that is a case with the logo swapped is the failure mode, not the shortcut.
- **A probe that bypasses the event handlers tests a path nobody takes.** One case passed
  every probe-driven check — landing error 0.0000°, all slots reachable, both ends clamped — while
  being completely dead to a real scroll wheel. Every oracle must drive at least one **real** gesture,
  and `verify_case.py` now does this on every page.
- **Placeholder geometry passes every oracle and still looks like nothing.** Grey gradient rectangles
  standing in for photographs clear the type-ratio, dead-beat and state-matrix checks and still read
  as a technique diagram. No metric detects it and none should be invented — decide whether the
  artwork exists **before** the build, not after (`cases/AUTHORING.md` Phase 0).
- **An oracle that pins its input and settles has not tested the page anyone uses.** Every assertion
  in `--strings` originally ran against a frozen clock; unpinned, a drifting endpoint or a body that
  never stops sloshing is invisible to all of them. Whatever your mechanic is driven by, add a pass
  that lets it run free. (And when a live readout looks alarming, check the loop is running first —
  a hidden browser tab suspends `requestAnimationFrame`, and a frozen sim reports plausible,
  completely wrong numbers: `cases/string-clock` 踩过的坑 #7.)
- **An inline style declaration outranks every rule in the stylesheet, including `:hover`.** A stack order written as `el.style.zIndex` silently kills `.thing:hover{z-index:…}` — the hover still *looks* live because its colour change comes from the same rule that did win, so the promotion is dead and nothing reports it. Ship per-element state as a custom property
  (`el.style.setProperty('--z', n)`) and let the rules read it.
- **Never build from a summary of a reference you could open yourself.** A previous session compressed a video into one sentence for a hand-off; building from that sentence produced a resizable window the reference does not have, an eased entrance it does not have, and a scrolling marquee that measures 0 px of movement per frame. The clip was on disk the whole time. A contact sheet gives pose; only the frames give **trajectory, cadence and stillness**
 .
- **An oracle threshold comes from a measurement or from a design requirement you can state — never from taste.** A "no clumping" band of nearest-neighbour CV [0.08, 0.40] looked principled and the reference itself scored 0.507: it places at random and lets cards overlap hard. The real contract was elsewhere — **z-order is arrival order, so the newest card is never buried** — and that one is worth asserting.
- **Decide what the page is *drawn with* before you decide what it does.** Seven cases shipped as DOM + CSS on the system font stack while every reference was something else; nothing in the skill ever asked the question, so every brief resolved to divs by default. The layer is a declaration in the case README, and `verify_case.py --layer` checks it (`references/render-layer.md` §1, `cases/ink-crowd`).
- **Some numbers only exist in the source.** The 11 Hz boil clock that decides whether a frame reads as drawn is invisible to frame-differencing: sub-pixel displacement under camera motion, 95 % of frames differ, autocorrelation flat. Measuring pixels would have produced a confident wrong answer. Fetch the bundle and read it (`cases/ink-crowd` §1).
- **"Take the feel, not the layout" means taking the motion first.** A build that lifted a reference's palette and grain and shipped a static page passed every gate in the floor: the page looked right and did nothing. Characterise the reference's motion in numbers before touching the design — per-layer velocity and how much of the time anything is moving — and assert liveness with no input.
- **Whole-frame correlation cannot measure a multi-layer scene.** Layers moving at different rates, some in opposition, cancel to a median shift of zero. Track one colour or feature mask per layer instead.
- **Check a tiled texture's period against every repeating dimension near it.** A 128 px noise tile over an 11-band strip on a 1440 px canvas (130.9 px per band) beats into vertical streaks — the one artefact film grain never has. Writing noise into the pixels avoids the whole class.
- **A pointer that aims into a 3D scene must intersect the plane the *content* sits on, not the ground.** Figures are 1.75 units tall; a ray aimed at a body and intersected with `y = 0` lands far behind it, and near the horizon it runs away entirely. The symptom is not "slightly off" — it is a mechanic that works in one screen band and is completely deaf everywhere else, while its own counter still reports activity (`cases/ink-crowd` §4, `verify_case.py --follow`).
- **Two answers to "pin a screen", and they exclude each other.** Hijack the wheel and translate a rail: you can compute any progress you like, and `position:sticky` will never fire because the ancestor is transformed, so every pinned child must be parked by hand. Leave scroll alone and stack `sticky; top:0; height:100vh` screens: pinning is free, and progress has to be read back out of the document as `(scrollY - offsetTop) / innerHeight`. Pick before laying anything out; retrofitting either onto the other is a rewrite (`cases/wheel-rail` §2, `cases/press-stack` §2).
- **A sticky stack of one-viewport screens has no dwell at all.** Each screen is fully visible at exactly one scroll position, so every animation inside it plays in the half already being covered, and scrolling reads as dead. `margin-bottom: 100vh` on each screen buys a viewport of dwell without changing its box. Then progress splits: content uses `(scrollY - top) / vh` while the screen is whole, and only exit effects use `(scrollY - top - vh) / vh` (`cases/press-stack` §2).
- **Read the layout in the oracle; never assume it.** `--stack` hardcoded "one screen per viewport" and reported 1 of 8 pinned the moment the layout gained a dwell — a green-to-red flip caused entirely by the check, not the page. Section positions come from the probe (`cases/press-stack` §3).
- **A sticky stack eats the bottom of the screen first.** The next screen rides up from below, so anything anchored with `margin-top:auto` is the first thing covered. A headline, a giant numeral and a section rule all disappeared on the first build for this one reason. Content belongs in the upper two thirds (`cases/press-stack` §2).
- **Album covers are drawable; people are not.** Sleeves are a colour field, two shapes and a line of type; a record is concentric grooves plus two narrow specular arcs — a wide wash across the disc reads as a gold plate. Both are better generated than faked with a grey rectangle. A portrait or a hand is neither: leave the slot out and redesign around it rather than stacking primitives into a figure (`cases/press-stack` §4).
- **Density is a device, and the underlay is where it comes from.** A page can carry every motion mechanism the reference has and still read thin: 184 elements against the reference's 584. The gap was not more copy — it was the technical-drawing underlay the reference puts beneath everything: a hairline grid, act rules, range markers. Twenty elements, each of which draws itself and tracks the pointer, and the same screens stop looking like a wireframe of the reference (`cases/wheel-rail` §3).
- **A reference clip on disk outranks the live site.** Built from headless screenshots of `blink.trade`, this case reproduced the palette, the type and the scroll device and still got "the fuzzy round thing never changes". The clip showed the signature element is a radar sweep — a hand turning over a dotted ring, trailing a soft wedge that closes and clears in 0.92 s. A still frame cannot represent the one thing that made the page. **If a clip exists, watch it before building, even when the live site is reachable** (`cases/wheel-rail` §2b — and this is the second time it cost a rebuild).
- **Input-driven and ambient are not alternatives.** The correction "interaction motion means motion the mouse produces" is about what is *missing*, never a licence to strip the ambient layer. The reference runs both: wheel and pointer drive the layout and every reveal, and the sweep turns regardless. Assert both — churn under real input, and liveness with no input at all (`verify_case.py --rail`).
- **A rail that translates is not choreography.** A hijacked wheel moving a rail while every child element holds still is a scrollbar in costume — and it passed four `--rail` assertions before anyone looked at it. Measure how many elements actually change under real wheel input: the reference moves 64 %, the rejected build moved 13 %. Assert the churn, and assert that some elements are caught *mid*-transition, or a page that snaps between states satisfies the count and still reads as a slideshow (`cases/wheel-rail` §3).
- **Split the text or the page has nothing to animate.** The reference wraps every word in an inline-block span — 296 of them — at `translateY(8px)` / `opacity 0` with `transition-duration: 0s`, i.e. written per frame rather than handed to CSS. That single decision is most of the 64 %. Writing the values yourself is also what makes the reveal scrub backwards when the wheel reverses (`cases/wheel-rail` §2).
- **A scroll-mapped reveal on something already on screen resolves to "finished" on frame 1.** The hero diagram's draw-on never once played, because its section's `top` is 0 at rest so the progress clamps to 1 immediately. Anything visible at rest needs its own load clock; only what starts below the fold can be mapped to scroll (`cases/wheel-rail` §2).
- **Interaction motion means motion the mouse produces.** Wheel and pointer, nothing else. A page that drifts, breathes and dissolves on its own clock while the mouse sits still is a scre

…(truncated)
