|
|
|
|
@@ -1,27 +1,42 @@
|
|
|
|
|
/* ==========================================================================
|
|
|
|
|
Round 5: one card rule, and the motion pass.
|
|
|
|
|
|
|
|
|
|
The rule: a chapter title sits outside, above its plate. Every card title
|
|
|
|
|
sits inside its card, first, under a mono registration plate in the
|
|
|
|
|
form "EXIT.N / KIND", the same plate the project cards already carry.
|
|
|
|
|
Plates are decorative and generated by CSS (alt text ''), so the server
|
|
|
|
|
and hydrated text stay identical and screen readers hear titles only.
|
|
|
|
|
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 is transform, clip-path and colour only, never on the chapter
|
|
|
|
|
headings (journey and audio measure them), and only with
|
|
|
|
|
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; }
|
|
|
|
|
|
|
|
|
|
.experience-record h3::before,
|
|
|
|
|
.plate--record h3::before,
|
|
|
|
|
.systems-operations h3::before,
|
|
|
|
|
.infrastructure-case > h3::before,
|
|
|
|
|
.toolkit-group h3::before {
|
|
|
|
|
.plate--grid h3::before {
|
|
|
|
|
display: block;
|
|
|
|
|
inline-size: max-content;
|
|
|
|
|
max-inline-size: 100%;
|
|
|
|
|
@@ -34,36 +49,29 @@
|
|
|
|
|
letter-spacing: 0.14em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
.experience-record h3::before { content: '03.' counter(role) ' / Role' / ''; }
|
|
|
|
|
.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' / ''; }
|
|
|
|
|
.toolkit-group h3::before { content: '07.' counter(stack) ' / Stack' / ''; }
|
|
|
|
|
.plate--grid h3::before { content: '07.' counter(stack) ' / Stack' / ''; }
|
|
|
|
|
|
|
|
|
|
@media (forced-colors: active) {
|
|
|
|
|
.experience-record h3::before,
|
|
|
|
|
.systems-operations h3::before,
|
|
|
|
|
.infrastructure-case > h3::before,
|
|
|
|
|
.toolkit-group h3::before {
|
|
|
|
|
.plate h3::before {
|
|
|
|
|
border-color: CanvasText;
|
|
|
|
|
color: CanvasText;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@media print {
|
|
|
|
|
.experience-record h3::before,
|
|
|
|
|
.plate--record h3::before,
|
|
|
|
|
.systems-operations h3::before,
|
|
|
|
|
.infrastructure-case > h3::before,
|
|
|
|
|
.toolkit-group h3::before { display: none; }
|
|
|
|
|
.plate--grid h3::before { display: none; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- Tailored cards ---------- */
|
|
|
|
|
/* Project cards and the operations card are panels: a lit top edge in the
|
|
|
|
|
chapter accent, which the hover draws out across the card. */
|
|
|
|
|
.project-card,
|
|
|
|
|
.systems-operations {
|
|
|
|
|
/* ---------- Panels: a lit top edge in the chapter accent ---------- */
|
|
|
|
|
.plate {
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
.project-card::after,
|
|
|
|
|
.systems-operations::after {
|
|
|
|
|
.plate--panel::after {
|
|
|
|
|
content: '' / '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: 0 0 auto;
|
|
|
|
|
@@ -77,70 +85,73 @@
|
|
|
|
|
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) {
|
|
|
|
|
.project-card::after,
|
|
|
|
|
.systems-operations::after { display: none; }
|
|
|
|
|
.plate--panel::after { display: none; }
|
|
|
|
|
.plate:has(:focus-visible) { outline-color: Highlight; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- Motion ---------- */
|
|
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
|
|
|
/* Hover and focus: cards lift, their accent edge runs across. */
|
|
|
|
|
.project-card,
|
|
|
|
|
.systems-operations,
|
|
|
|
|
.experience-record,
|
|
|
|
|
.toolkit-group {
|
|
|
|
|
.plate {
|
|
|
|
|
transition:
|
|
|
|
|
transform 420ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
|
|
|
box-shadow 420ms ease;
|
|
|
|
|
transform var(--dur-ui) var(--ease-arrive),
|
|
|
|
|
box-shadow var(--dur-ui) ease;
|
|
|
|
|
}
|
|
|
|
|
.project-card::after,
|
|
|
|
|
.systems-operations::after {
|
|
|
|
|
transition: transform 620ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
|
|
|
.plate--panel::after {
|
|
|
|
|
transition: transform var(--dur-entrance) var(--ease-arrive);
|
|
|
|
|
}
|
|
|
|
|
.project-card:is(:hover, :focus-within)::after,
|
|
|
|
|
.systems-operations:is(:hover, :focus-within)::after {
|
|
|
|
|
transform: scaleX(1);
|
|
|
|
|
}
|
|
|
|
|
.project-card:is(:hover, :focus-within),
|
|
|
|
|
.systems-operations:is(:hover, :focus-within) {
|
|
|
|
|
.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);
|
|
|
|
|
}
|
|
|
|
|
.experience-record:is(:hover, :focus-within),
|
|
|
|
|
.toolkit-group:is(:hover, :focus-within) {
|
|
|
|
|
.plate:is(.plate--record, .plate--grid):is(:hover, :has(:focus-visible)) {
|
|
|
|
|
transform: translateX(6px);
|
|
|
|
|
}
|
|
|
|
|
.toolkit-item svg,
|
|
|
|
|
.toolkit-item img {
|
|
|
|
|
transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
|
|
|
transition: transform var(--dur-micro) var(--ease-arrive);
|
|
|
|
|
}
|
|
|
|
|
.toolkit-item:hover svg,
|
|
|
|
|
.toolkit-item:hover img {
|
|
|
|
|
transform: translateY(-2px) scale(1.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Card reveals: each card rises and unmasks as it enters the reading
|
|
|
|
|
window. Scroll-linked, so it can never lag behind the reader. */
|
|
|
|
|
/* 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 .project-card,
|
|
|
|
|
.reading-stage .systems-operations,
|
|
|
|
|
.reading-stage .experience-record,
|
|
|
|
|
.reading-stage .toolkit-group,
|
|
|
|
|
.reading-stage .plate,
|
|
|
|
|
.reading-stage .infrastructure-node {
|
|
|
|
|
animation: card-reveal linear both;
|
|
|
|
|
animation: plate-entrance linear both;
|
|
|
|
|
animation-timeline: view();
|
|
|
|
|
animation-range: entry 0% entry 55%;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Chapter transitions: the arriving chapter's plate sweeps a light edge
|
|
|
|
|
across and its first cards settle in sequence. */
|
|
|
|
|
.reading-stage .portfolio-section[data-current] .chapter-body > :is(p, dl, ul, .experience-timeline, .toolkit-grid) {
|
|
|
|
|
animation: chapter-settle 900ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
|
|
|
@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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.reading-stage .portfolio-section[data-current] .chapter-body > :nth-child(2) { animation-delay: 90ms; }
|
|
|
|
|
.reading-stage .portfolio-section[data-current] .chapter-body > :nth-child(3) { animation-delay: 180ms; }
|
|
|
|
|
.reading-stage .portfolio-section[data-current] .chapter-body > :nth-child(n + 4) { animation-delay: 270ms; }
|
|
|
|
|
|
|
|
|
|
/* The gantry's lane lamps light in sequence along the route. */
|
|
|
|
|
html .site-navigation li:nth-child(2)::after { transition-delay: 40ms; }
|
|
|
|
|
@@ -152,17 +163,13 @@
|
|
|
|
|
html .site-navigation li:nth-child(8)::after { transition-delay: 280ms; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes card-reveal {
|
|
|
|
|
@keyframes plate-entrance {
|
|
|
|
|
from {
|
|
|
|
|
transform: translateY(36px);
|
|
|
|
|
clip-path: inset(0 0 22% 0);
|
|
|
|
|
translate: 0 28px;
|
|
|
|
|
clip-path: inset(0 0 100% 0);
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
transform: none;
|
|
|
|
|
translate: none;
|
|
|
|
|
clip-path: inset(-40px -40px -40px -40px);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@keyframes chapter-settle {
|
|
|
|
|
from { transform: translateY(14px); }
|
|
|
|
|
to { transform: none; }
|
|
|
|
|
}
|
|
|
|
|
|