1f695d3932
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
39 lines
3.6 KiB
Markdown
39 lines
3.6 KiB
Markdown
# ANIMATION_SPEC.md
|
||
Technical realisation of the Phase 1 motion system (MOTION_GUIDELINES + ANIMATION_CONCEPTS A1–A10). Library decision: **none** (TECH_SPEC §4) — CSS-first + the shared observer module.
|
||
|
||
---
|
||
|
||
## 1. Token implementation
|
||
|
||
Motion tokens are CSS custom properties (`--dur-instant: 80ms` … `--dur-signature: 700ms`, `--ease-out`, `--ease-in-out`) defined once in `motion.css`; every transition/keyframe references tokens. A single `@media (prefers-reduced-motion: reduce)` layer overrides: durations → 1ms (not 0 — ensures transitionend semantics still fire), translations → none, trace/pulse keyframes → final frame. No per-component reduced-motion logic anywhere.
|
||
|
||
## 2. Concept → mechanism map
|
||
|
||
| Concept (Phase 1) | Mechanism | Trigger |
|
||
|---|---|---|
|
||
| A1 Trace draw | Inline SVG, `stroke-dasharray/dashoffset` keyframe, dot `opacity` blink ×1 | CSS animation on load; `sessionStorage` flag adds `data-seen` on repeat visits → static |
|
||
| A2 Section arrive | `[data-reveal]` class toggle → `opacity` + `translateY(12px)` transition; children staggered via `--reveal-index` custom property delay | `observer.ts`, threshold 0.2, once |
|
||
| A3 Card focus | Pure CSS `:hover`/`:focus-visible` (border token, transform 4px, underline scale, arrow translate) | none (CSS) |
|
||
| A4 Chip pulse | CSS keyframe ×2 iterations on `.status-active .dot`, started when `[data-reveal]` fires (animation-play-state pattern) | observer |
|
||
| A5 Diagram flow | SVG with class-annotated nodes/edges; `:hover` on node → CSS sibling/descendant rules highlight edges, dim others via `opacity` | CSS only, `@media (hover: hover)` gate |
|
||
| A6 Theme morph | Two-path SVG icon crossfade/morph via CSS; global colour transition on `data-theme` swap limited to a curated property list (avoid transitioning *everything*) | `theme.ts` |
|
||
| A7 Language cross-fade | View Transitions API (same-document for the switch control; cross-document for navigation) with CSS-only fallback = normal navigation | browser-native, progressive |
|
||
| A8 Timeline grow | Spine is a scaleY-transformed pseudo-element; dots opacity-staggered | observer |
|
||
| A9 Form confirm | Panel swap with height-locked container; check icon `stroke-dashoffset` draw | `form.ts` on 2xx |
|
||
| A10 404 fray | Static SVG with a short CSS keyframe (dash gap growth); reduced-motion static | load |
|
||
| Header compress | `data-scrolled` boolean flag at >120px (observer sentinel element) → CSS height/blur transition. *Deviation from Phase 1:* linear scroll-mapping downgraded to a threshold toggle with smooth transition — visually near-identical, avoids scroll-linked JS entirely; scroll-driven CSS animations may restore true mapping as progressive enhancement where supported | observer |
|
||
|
||
## 3. Performance rules (enforceable)
|
||
|
||
- Animatable properties allow-list: `transform`, `opacity`, `stroke-dashoffset`, plus theme-swap colour transitions. Anything else fails review.
|
||
- No `will-change` except trace + lightbox transitions (added/removed around use).
|
||
- Observer instances: exactly one shared IntersectionObserver for reveals/spy + one sentinel for header. No scroll event listeners anywhere.
|
||
- Lighthouse TBT budget (<100 ms) is the regression tripwire for animation JS.
|
||
|
||
## 4. Accessibility rules
|
||
|
||
- Reduced-motion layer per §1 — verified in Playwright with emulated `prefers-reduced-motion`.
|
||
- Nothing flashes >3×/s (chip pulse is 2 iterations, ~1 Hz). No parallax beyond 4px.
|
||
- Focus is never moved by animation; reveals never contain focusable elements while hidden (visibility handled so tab order is stable pre-reveal).
|
||
- View-transition language swap must not discard focus position: switch control retains focus after navigation (tested).
|