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

3.6 KiB
Raw Blame History

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).