033c9ec315
- vitest: dictionary parity, slug-map bijection, content-schema validation, JSON-LD (23) - playwright: both locales, language-switch mapping (incl. no-JS), CV downloads, form happy-path + honeypot, axe a11y on 5 templates x 2 themes (23) - fix: raise ink-faint + light accent to meet WCAG AA 4.5:1 (axe-verified) - fix: gate reveal animations behind html.js so content is visible without JS (progressive enhancement - was hidden at opacity 0); scan reduced-motion path - fix: validate contact form before the anti-bot time-trap (no false success) - chore: ESLint node globals, typed diagram props, resvg fontFiles, prettier pass Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
134 lines
3.0 KiB
CSS
134 lines
3.0 KiB
CSS
/*
|
|
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;
|
|
}
|
|
}
|