/* ========================================================================== Round 5: one card rule, and the motion pass. The rule: a chapter title sits outside, above its plate. Every card is a .plate and its title sits inside it, first, under a mono registration plate in the form "EXIT.N / KIND", the same plate the project cards already carry. Three variants: .plate--panel standalone cards: a lit top edge that runs on hover .plate--record timeline entries: they step along the route on hover .plate--grid toolkit groups: the same step, in a grid Registration plates are decorative and generated by CSS (alt text ''), so the server and hydrated text stay identical and screen readers hear titles only. Motion tokens: arrivals ease out-expo, departures ease in-quart, colour eases. 160 ms micro, 320 ms UI, 720 ms entrance, 900-1400 ms cinematic. Transform, clip-path and colour only, never on the chapter headings (journey and audio measure them), and only with prefers-reduced-motion: no-preference. ========================================================================== */ :root { --ease-arrive: cubic-bezier(0.22, 1, 0.36, 1); --ease-depart: cubic-bezier(0.5, 0, 0.75, 0); --dur-micro: 160ms; --dur-ui: 320ms; --dur-entrance: 720ms; } /* ---------- Registration plates for the cards that lacked one ---------- */ .experience-timeline { counter-reset: role; } .experience-timeline > li { counter-increment: role; } .toolkit-grid { counter-reset: stack; } .toolkit-group { counter-increment: stack; } .plate--record h3::before, .systems-operations h3::before, .infrastructure-case > h3::before, .plate--grid h3::before { display: block; inline-size: max-content; max-inline-size: 100%; margin-block-end: 0.85rem; padding: 0.34rem 0.6rem 0.3rem; border: 1px solid color-mix(in srgb, var(--chapter-accent, var(--accent)) 55%, transparent); border-radius: 3px; color: color-mix(in srgb, var(--chapter-accent, var(--accent)) 72%, var(--text)); font: 650 0.64rem/1.2 var(--mono); letter-spacing: 0.14em; text-transform: uppercase; } .plate--record h3::before { content: '03.' counter(role) ' / Role' / ''; } .systems-operations h3::before { content: '03.S / Operations' / ''; } .infrastructure-case > h3::before { content: '04.1 / Platform' / ''; } .plate--grid h3::before { content: '07.' counter(stack) ' / Stack' / ''; } @media (forced-colors: active) { .plate h3::before { border-color: CanvasText; color: CanvasText; } } @media print { .plate--record h3::before, .systems-operations h3::before, .infrastructure-case > h3::before, .plate--grid h3::before { display: none; } } /* ---------- Panels: a lit top edge in the chapter accent ---------- */ .plate { position: relative; } .plate--panel::after { content: '' / ''; position: absolute; inset: 0 0 auto; block-size: 2px; background: linear-gradient( 90deg, color-mix(in srgb, var(--chapter-accent, var(--accent)) 85%, transparent), transparent ); pointer-events: none; transform: scaleX(0.34); transform-origin: left; } /* Keyboard focus inside a card reads the same as hover, plus an outline. */ .plate:has(:focus-visible) { outline: 2px solid var(--focus); outline-offset: 4px; } .plate--panel:is(:hover, :has(:focus-visible))::after { transform: scaleX(1); } @media (forced-colors: active) { .plate--panel::after { display: none; } .plate:has(:focus-visible) { outline-color: Highlight; } } /* ---------- Motion ---------- */ @media (prefers-reduced-motion: no-preference) { .plate { transition: transform var(--dur-ui) var(--ease-arrive), box-shadow var(--dur-ui) ease; } .plate--panel::after { transition: transform var(--dur-entrance) var(--ease-arrive); } .plate--panel:is(:hover, :has(:focus-visible)) { transform: translateY(-4px); box-shadow: 0 22px 48px -24px color-mix(in srgb, var(--chapter-accent, var(--accent)) 55%, transparent); } .plate:is(.plate--record, .plate--grid):is(:hover, :has(:focus-visible)) { transform: translateX(6px); } .toolkit-item svg, .toolkit-item img { transition: transform var(--dur-micro) var(--ease-arrive); } .toolkit-item:hover svg, .toolkit-item:hover img { transform: translateY(-2px) scale(1.12); } /* Entrances animate translate, so the hover transform still composes. Each plate rises 28 px and unmasks top-down as it enters the reading window, scroll-linked so it can never lag the reader. Browsers without view timelines get the same entrance from an IntersectionObserver class (useCardEntrances). */ @supports (animation-timeline: view()) { .reading-stage .plate, .reading-stage .infrastructure-node { animation: plate-entrance linear both; animation-timeline: view(); animation-range: entry 0% entry 55%; } } @supports not (animation-timeline: view()) { .reading-stage[data-entrances] .plate:not([data-entered]), .reading-stage[data-entrances] .infrastructure-node:not([data-entered]) { translate: 0 28px; clip-path: inset(0 0 100% 0); } .reading-stage[data-entrances] .plate[data-entered], .reading-stage[data-entrances] .infrastructure-node[data-entered] { transition: translate var(--dur-entrance) var(--ease-arrive), clip-path var(--dur-entrance) var(--ease-arrive), transform var(--dur-ui) var(--ease-arrive), box-shadow var(--dur-ui) ease; } } /* The gantry's lane lamps light in sequence along the route. */ html .site-navigation li:nth-child(2)::after { transition-delay: 40ms; } html .site-navigation li:nth-child(3)::after { transition-delay: 80ms; } html .site-navigation li:nth-child(4)::after { transition-delay: 120ms; } html .site-navigation li:nth-child(5)::after { transition-delay: 160ms; } html .site-navigation li:nth-child(6)::after { transition-delay: 200ms; } html .site-navigation li:nth-child(7)::after { transition-delay: 240ms; } html .site-navigation li:nth-child(8)::after { transition-delay: 280ms; } } @keyframes plate-entrance { from { translate: 0 28px; clip-path: inset(0 0 100% 0); } to { translate: none; clip-path: inset(-40px -40px -40px -40px); } }