/* Motion layer (ANIMATION_SPEC). Only transform / opacity / stroke-dashoffset animate. Reduced-motion is a single global override — not per-component JS (ANIMATION_SPEC §1). */ /* ---- Reveal (A2): opacity + 12px rise, staggered by --reveal-index ---- Gated behind html.js so content stays visible with no JavaScript (progressive enhancement — content is never hidden by animation). */ html.js [data-reveal] { opacity: 0; transform: translateY(12px); transition: opacity var(--dur-entrance) var(--ease-out), transform var(--dur-entrance) var(--ease-out); transition-delay: calc(var(--reveal-index, 0) * 60ms); } html.js [data-reveal].is-revealed { opacity: 1; transform: none; } /* ---- Trace draw (A1) ---- */ @keyframes trace-draw { from { stroke-dashoffset: var(--trace-len, 600); } to { stroke-dashoffset: 0; } } @keyframes dot-blink { 0%, 100% { opacity: 1; } 40% { opacity: 0.2; } } .trace-line { stroke-dasharray: var(--trace-len, 600); stroke-dashoffset: var(--trace-len, 600); animation: trace-draw var(--dur-signature) var(--ease-in-out) forwards; } [data-seen] .trace-line { animation: none; stroke-dashoffset: 0; } .trace-dot { animation: dot-blink 900ms var(--ease-out) 700ms 1; } /* ---- Status chip pulse (A4): 2 iterations, ~1Hz ---- */ @keyframes chip-pulse { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.4); opacity: 0.55; } 100% { transform: scale(1); opacity: 1; } } .is-revealed .status-dot--active { animation: chip-pulse 900ms var(--ease-out) 2; } /* ---- Timeline spine grow (A8) ---- */ html.js .timeline-spine { transform: scaleY(0); transform-origin: top; transition: transform var(--dur-entrance) var(--ease-out); } .is-revealed .timeline-spine { transform: scaleY(1); } /* ---- 404 fray (A10) ---- */ @keyframes fray { from { stroke-dashoffset: 0; } to { stroke-dashoffset: 8; } } /* ---- Check-draw for form success (A9) ---- */ @keyframes check-draw { to { stroke-dashoffset: 0; } } .check-path { stroke-dasharray: 24; stroke-dashoffset: 24; animation: check-draw var(--dur-standard) var(--ease-out) forwards; } /* ---- View-transition language/route cross-fade (A6/A7) ---- */ ::view-transition-old(root), ::view-transition-new(root) { animation-duration: var(--dur-quick); } /* ============================================================ Reduced motion: durations collapse, translations disabled, animated states render at their final frame (no content lost). ============================================================ */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; transition-duration: 1ms !important; scroll-behavior: auto !important; } html.js [data-reveal] { opacity: 1 !important; transform: none !important; } .trace-line { stroke-dashoffset: 0; } .timeline-spine { transform: scaleY(1) !important; } }