Files
ResumeSite/site/src/styles/motion.css
T
cesnimda 033c9ec315 test: vitest unit + playwright e2e suites; fix a11y, contrast, no-JS reveal
- 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>
2026-07-04 06:14:18 +02:00

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;
}
}