Files
ResumeSite/docs/spec/ANIMATION_SPEC.md
T
2026-07-03 21:07:25 +02:00

39 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ANIMATION_SPEC.md
Technical realisation of the Phase 1 motion system (MOTION_GUIDELINES + ANIMATION_CONCEPTS A1A10). 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).