feat: one card title rule and a motion pass

Every card title now sits first inside its card under a mono registration
plate; chapter titles stay outside, above the plate. Cards rise and unmask
as they enter the reading window, lift on hover and focus, chapters settle
in sequence on arrival and the gantry lane lamps light along the route.
All motion is transform and clip-path, behind reduced-motion.
This commit is contained in:
2026-10-10 22:46:06 +03:00
parent 582bf6dc43
commit c3863f8dbe
4 changed files with 169 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -13,6 +13,7 @@ import './styles/title-navigation.css';
import './styles/wayfinding.css'; import './styles/wayfinding.css';
import './styles/route.css'; import './styles/route.css';
import './styles/gantry.css'; import './styles/gantry.css';
import './styles/cards.css';
import './styles/print.css'; import './styles/print.css';
defineProps<{ inputs: Readonly<ProductionInputs> }>(); defineProps<{ inputs: Readonly<ProductionInputs> }>();

168
src/styles/cards.css Normal file
View File

@@ -0,0 +1,168 @@
/* ==========================================================================
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.
Motion is transform, clip-path and colour only, never on the chapter
headings (journey and audio measure them), and only with
prefers-reduced-motion: no-preference.
========================================================================== */
/* ---------- 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,
.systems-operations h3::before,
.infrastructure-case > h3::before,
.toolkit-group 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;
}
.experience-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' / ''; }
@media (forced-colors: active) {
.experience-record h3::before,
.systems-operations h3::before,
.infrastructure-case > h3::before,
.toolkit-group h3::before {
border-color: CanvasText;
color: CanvasText;
}
}
@media print {
.experience-record h3::before,
.systems-operations h3::before,
.infrastructure-case > h3::before,
.toolkit-group 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 {
position: relative;
}
.project-card::after,
.systems-operations::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;
}
@media (forced-colors: active) {
.project-card::after,
.systems-operations::after { display: none; }
}
/* ---------- 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 {
transition:
transform 420ms cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 420ms ease;
}
.project-card::after,
.systems-operations::after {
transition: transform 620ms cubic-bezier(0.22, 1, 0.36, 1);
}
.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) {
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) {
transform: translateX(6px);
}
.toolkit-item svg,
.toolkit-item img {
transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
}
.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. */
@supports (animation-timeline: view()) {
.reading-stage .project-card,
.reading-stage .systems-operations,
.reading-stage .experience-record,
.reading-stage .toolkit-group,
.reading-stage .infrastructure-node {
animation: card-reveal 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;
}
.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; }
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 card-reveal {
from {
transform: translateY(36px);
clip-path: inset(0 0 22% 0);
}
to {
transform: none;
clip-path: inset(-40px -40px -40px -40px);
}
}
@keyframes chapter-settle {
from { transform: translateY(14px); }
to { transform: none; }
}