/* Home page only. */

.page { position: relative; overflow-x: clip; }

.motion-toggle-input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.motion-toggle {
  display: flex; align-items: center; gap: 8px;
  background: transparent; border: 1px solid oklch(0.97 0.008 255 / 0.3);
  border-radius: 999px; padding: 7px 14px 7px 10px; cursor: pointer;
  font-family: 'Space Grotesk', sans-serif; font-size: 11px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: oklch(0.9 0.008 255);
  transition: border-color 0.2s, background 0.2s; user-select: none;
}
.motion-toggle:hover { border-color: var(--accent1); }
.motion-toggle-dot {
  width: 8px; height: 8px; border-radius: 50%; background: oklch(0.6 0.02 250); flex-shrink: 0;
  transition: background 0.2s;
}
.motion-toggle-label-on { display: none; }
.motion-toggle-input:checked ~ .page-body .motion-toggle {
  border-color: var(--accent1); background: oklch(0.97 0.008 255 / 0.06);
}
.motion-toggle-input:checked ~ .page-body .motion-toggle .motion-toggle-dot { background: var(--accent1); }
.motion-toggle-input:checked ~ .page-body .motion-toggle-label-off { display: none; }
.motion-toggle-input:checked ~ .page-body .motion-toggle-label-on { display: inline; }
.motion-toggle-input:focus-visible ~ .page-body .motion-toggle { outline: 2px solid var(--accent1); outline-offset: 2px; }

/* Kill every animation/transition on the page instantly, either because the visitor's
   OS asks for reduced motion, or because they've flipped the toggle above. */
.motion-toggle-input:checked ~ .page-body,
.motion-toggle-input:checked ~ .page-body * {
  animation-duration: 0.01ms !important;
  animation-delay: 0s !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
  animation-timeline: auto !important;
}

/* ---------- hero ---------- */
.hero {
  position: relative;
  min-height: 92vh;
  display: flex; flex-direction: column; justify-content: center;
  padding: 80px 0 60px;
  overflow: hidden;
}
.hero-inner {
  position: relative; z-index: 2;
  max-width: 1440px; margin: 0 auto; width: 100%;
  padding: 0 clamp(20px, 5vw, 64px);
}
.hero-skyline {
  position: absolute; inset: -20px; z-index: 0; overflow: hidden;
  filter: blur(6px) saturate(1.1);
  /* the video's own poster, so hiding the video still leaves the skyline */
  background: url(../media/toronto-poster.jpg) center 65% / cover no-repeat;
}
.hero-skyline video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center 65%; display: block;
}
.hero-cloud {
  position: absolute; border-radius: 50%; pointer-events: none;
  background: radial-gradient(ellipse at center, oklch(0.97 0.01 255 / 0.6), oklch(0.97 0.01 255 / 0) 70%);
}
.cloud-1 { width: 380px; height: 130px; top: 6%; left: -8%; animation: driftCloud 46s ease-in-out infinite; }
.cloud-2 { width: 280px; height: 100px; top: 16%; left: 34%; animation: driftCloud 60s ease-in-out infinite reverse; animation-delay: -12s; }
.cloud-3 { width: 320px; height: 110px; top: 3%; left: 62%; animation: driftCloud 52s ease-in-out infinite; animation-delay: -24s; }
.cloud-4 { width: 220px; height: 80px; top: 24%; left: 82%; animation: driftCloud 38s ease-in-out infinite reverse; animation-delay: -6s; }
@keyframes driftCloud { 0%, 100% { transform: translateX(-30px); } 50% { transform: translateX(50px); } }
.hero-overlay {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, oklch(0.16 0.02 255 / 0.5) 0%, oklch(0.16 0.02 255 / 0.7) 55%, oklch(0.16 0.02 255 / 0.96) 100%);
}
.hero-loc {
  display: flex; align-items: center; gap: 8px; margin: 0 0 16px;
  color: oklch(0.94 0.01 255); opacity: 0.9;
  filter: drop-shadow(0 1px 5px oklch(0.16 0.02 255 / 0.4));
}
.hero-loc-pin { flex-shrink: 0; }
.hero-loc-city {
  font-size: 13px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
}

.eyebrow {
  font-size: 13px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase;
  color: oklch(0.7 0.03 250); margin: 0 0 20px; position: relative; z-index: 2;
}
.hero-title {
  margin: 0; line-height: 0.86; letter-spacing: -0.01em;
  font-size: clamp(2.2rem, 12vw, 9.5rem);
  white-space: nowrap;
  position: relative; z-index: 2;
  font-family: 'Archivo', sans-serif; font-weight: 700;
}
.letter {
  display: inline-block; opacity: 0;
  animation: letterIn 0.7s cubic-bezier(.16,1,.3,1) both;
}
@keyframes letterIn {
  from { opacity: 0; transform: translateY(0.5em) rotate(-6deg); }
  to { opacity: 1; transform: none; }
}
.hero-sub {
  max-width: 620px; margin: 28px 0 0; font-size: clamp(1rem, 1.6vw, 1.25rem);
  line-height: 1.5; color: oklch(0.85 0.015 250); position: relative; z-index: 2;
}
.hero-cta { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 40px; position: relative; z-index: 2; }
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 14px 26px; font-weight: 700; font-size: 14px; letter-spacing: 0.03em;
  border: 2px solid oklch(0.97 0.008 255);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.btn-primary { background: var(--accent1); color: oklch(0.16 0.02 255); box-shadow: 6px 6px 0 var(--accent2); }
.btn-primary:hover { transform: translate(-3px, -3px); box-shadow: 9px 9px 0 var(--accent2); }
.btn-ghost { background: transparent; box-shadow: 6px 6px 0 var(--accent1); }
.btn-ghost:hover { transform: translate(-3px, -3px); box-shadow: 9px 9px 0 var(--accent1); }

/* ---------- marquee ---------- */
.marquee-strip {
  background: var(--accent1);
  overflow: hidden; padding: 16px 0; margin: 8px 0;
}
.marquee-track {
  display: flex; width: max-content; gap: 40px;
  animation: marqueeScroll 33s linear infinite;
}
@keyframes marqueeScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.marquee-item {
  font-family: 'Archivo Black', sans-serif; font-size: clamp(1rem, 2.2vw, 1.6rem);
  color: oklch(0.16 0.02 255); white-space: nowrap; display: flex; align-items: center; gap: 40px;
}
.marquee-item::after { content: '\2726'; font-size: 0.7em; opacity: 0.6; }

/* ---------- reveal ---------- */
@keyframes reveal { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: none; } }
.section-title, .work-card, .about-quote, .about-body, .stat {
  animation: reveal 0.7s cubic-bezier(.16,1,.3,1) both;
}
.work-card:nth-child(1) { animation-delay: 0s; }
.work-card:nth-child(2) { animation-delay: 0.12s; }
.work-card:nth-child(3) { animation-delay: 0.24s; }
.stat:nth-of-type(1) { animation-delay: 0s; }
.stat:nth-of-type(2) { animation-delay: 0.08s; }
.stat:nth-of-type(3) { animation-delay: 0.16s; }
.stat:nth-of-type(4) { animation-delay: 0.24s; }

/* ---------- work ---------- */
.work { padding: 140px clamp(20px, 5vw, 64px) 100px; max-width: 1440px; margin: 0 auto; }
.section-title {
  font-size: clamp(2.2rem, 6vw, 4.2rem); line-height: 0.95; margin: 0 0 64px;
}
.work-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px;
}
.work-card {
  border: 3px solid oklch(0.97 0.008 255); background: oklch(0.19 0.02 255);
  padding: 28px; display: flex; flex-direction: column; gap: 10px;
  box-shadow: 8px 8px 0 var(--accent1);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.work-card:hover { transform: translate(-5px, -5px); box-shadow: 13px 13px 0 var(--accent2); }
.work-title { margin: 8px 0 0; font-size: 1.35rem; font-weight: 700; }
.work-company { margin: 0; font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; color: oklch(0.62 0.02 250); }
.work-desc { margin: 6px 0 0; font-size: 15px; line-height: 1.55; color: oklch(0.85 0.01 250); flex-grow: 1; }
.work-metriclabel { margin: 0; font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: oklch(0.62 0.02 250); }
.work-metric-inline { font-family: 'Archivo Black', sans-serif; letter-spacing: 0; text-transform: none; font-size: 14px; color: var(--accent2); margin-right: 4px; }
.work-read { margin-top: 4px; font-size: 13px; font-weight: 700; color: var(--accent1); }

/* ---------- work preview (flow animation placeholder) ---------- */
.work-preview {
  position: relative; aspect-ratio: 16 / 9; margin: -28px -28px 20px;
  border-bottom: 2px solid oklch(0.97 0.008 255 / 0.3);
  background: oklch(0.14 0.02 255);
  display: flex; align-items: center; padding: 0 22px; overflow: hidden;
}
.wp-tag {
  position: absolute; top: 10px; left: 10px; font-size: 10px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: oklch(0.6 0.02 250);
}
.wp-line { position: absolute; left: 14%; right: 14%; top: 50%; height: 2px; background: oklch(0.97 0.008 255 / 0.18); }
.wp-nodes { position: relative; width: 100%; display: flex; justify-content: space-between; z-index: 1; }
.wp-node {
  width: 11px; height: 11px; border-radius: 50%; background: oklch(0.97 0.008 255 / 0.35);
  animation: wpPulse 2.6s ease-in-out infinite;
}
.wp-node:nth-child(1) { animation-delay: 0s; }
.wp-node:nth-child(2) { animation-delay: 0.5s; }
.wp-node:nth-child(3) { animation-delay: 1s; }
@keyframes wpPulse { 0%, 100% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.35); opacity: 1; } }
.wp-traveler {
  position: absolute; top: 50%; left: 14%; width: 14px; height: 14px; margin: -7px 0 0 -7px;
  border-radius: 50%; background: var(--accent1); box-shadow: 0 0 14px var(--accent1);
  animation: wpTravel 3.6s cubic-bezier(.65,0,.35,1) infinite;
}
.work-card-inert .wp-traveler { background: var(--accent2); box-shadow: 0 0 14px var(--accent2); }
@keyframes wpTravel {
  0% { left: 14%; }
  42% { left: 50%; }
  50% { left: 50%; }
  92% { left: 86%; }
  100% { left: 14%; }
}

/* ---------- stats ---------- */
.stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  border-top: 2px solid oklch(0.97 0.008 255 / 0.15);
  border-bottom: 2px solid oklch(0.97 0.008 255 / 0.15);
  max-width: 1440px; margin: 0 auto; padding: 0 clamp(20px, 5vw, 64px);
}
.stat {
  padding: 44px 20px; text-align: center; border-left: 1px solid oklch(0.97 0.008 255 / 0.12);
}
.stat:first-of-type { border-left: none; }
.stat-value { font-family: 'Archivo Black', sans-serif; font-size: clamp(2rem, 4.5vw, 3.2rem); color: var(--accent1); }
.stat-label { margin-top: 6px; font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: oklch(0.65 0.02 250); }

/* ---------- about ---------- */
.about {
  padding: 140px clamp(20px, 5vw, 64px);
  max-width: 1440px; margin: 0 auto;
  display: grid; grid-template-columns: 1fr 1.15fr; gap: 60px; align-items: start;
}
@media (max-width: 640px) {
  .about { grid-template-columns: 1fr; gap: 32px; padding: 80px 24px; }
}
.about-quote p {
  font-family: 'Archivo Black', sans-serif; font-size: clamp(2rem, 4.5vw, 3.4rem);
  line-height: 1.04; margin: 0; color: var(--accent1);
}
.about-body p { font-size: 17px; line-height: 1.7; color: oklch(0.87 0.01 250); margin: 0 0 20px; }
.about-badge {
  display: inline-block; margin-top: 12px; padding: 10px 18px;
  border: 2px dashed oklch(0.97 0.008 255 / 0.5);
  font-size: 12px; font-weight: 700; letter-spacing: 0.1em;
}

/* ---------- contact ---------- */
.contact {
  background: oklch(0.12 0.02 255);
  padding: 120px 0 60px;
  border-top: 3px solid oklch(0.97 0.008 255);
}
.contact-inner { max-width: 1440px; margin: 0 auto; padding: 0 clamp(20px, 5vw, 64px); }
.contact-title {
  font-size: clamp(2.6rem, 8vw, 6rem); line-height: 0.92; margin: 0 0 48px; color: oklch(0.97 0.008 255);
}
.contact-links { display: flex; flex-wrap: wrap; gap: 28px 40px; margin-bottom: 60px; }
.contact-links a {
  font-size: clamp(1rem, 2vw, 1.3rem); font-weight: 700; border-bottom: 2px solid var(--accent1);
  padding-bottom: 4px; transition: color 0.2s;
}
.contact-links a:hover { color: var(--accent1); }
.copyright { font-size: 13px; color: oklch(0.65 0.02 250); margin: 0; display: flex; align-items: center; gap: 10px; }
.transit-dots { display: inline-flex; gap: 5px; }
.transit-dots .dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.transit-dots .dot-1 { background: #f7c02d; }
.transit-dots .dot-2 { background: #2e8540; }
.transit-dots .dot-4 { background: #92278f; }

/* A work card with no case study behind it yet: still reads as part of the set,
   but is visibly inert - no lift, no hover, no "read more" affordance. */
.work-card-inert { box-shadow: 8px 8px 0 oklch(0.45 0.015 250); border-color: oklch(0.72 0.015 250); }
.work-card-inert:hover { transform: none; box-shadow: 8px 8px 0 oklch(0.45 0.015 250); }
.work-card-inert .work-preview { opacity: 0.5; }
.work-read-soon { margin-top: 4px; display: inline-flex; align-items: center; color: oklch(0.62 0.02 250); }

/* ---------- work card previews: real screenshots ---------- */
/* Cards with a screenshot drop the flex/padding used by the animated placeholder
   so the image can bleed to the card edges. */
.work-preview.has-shot { display: block; padding: 0; }
.work-preview img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* ---------- work card previews: looping screen-flow filmstrip ---------- */
/* Each project's real screens, laid end to end. Dyne's track holds two copies
   of one wide composite back to back, so travelling one image-width (-50% of
   the track) loops seamlessly. Winterview instead uses separate per-screen
   files (cropped from that same original composite - easier to keep each
   screen legible and to redact text in just one of them) plus a trailing
   duplicate of its first image, so 4 equal flex items loop at -75% (3/4 of
   the track), landing back on a copy of the true first frame with no jump.
   Motion reads as someone clicking through the flow, not a conveyor belt:
   settle on a screen, then snap to the next with an eased step. */
/* Inset slightly so each project's brand tint reads as a backdrop the real
   screens sit on, instead of bleeding to the card edge like the plain shots do. */
.work-preview.has-shot.work-flow { padding: 16px; container-type: inline-size; }
.work-flow-winterview { background: oklch(0.53 0.12 258); }
.work-flow-dyne { background: oklch(0.55 0.18 25); }
.work-flow-track {
  display: flex; height: 100%; width: max-content;
  animation-iteration-count: infinite;
}
/* Each slide is 80% of the card's own width (80cqw, resolved against the
   .work-flow container regardless of the track's own auto width) with the
   real image centered inside via object-fit: contain - so a screen narrower
   or wider than the card is never left stranded against one edge.
   Narrower than the card on purpose: the current screen sits centered with a
   sliver of the previous and next screen peeking in on either side, so the
   motion still reads as a filmstrip rather than one screen replacing another.
   Every keyframe below adds +10cqw (half of the 20cqw left on the table) to
   shift that centered screen into the middle of the card instead of flush
   against its left edge.
   The trailing margin is the gap between screens (and, since both copies get
   it identically, also the gap at the seam between the two copies) - it
   doesn't disturb the -50% loop math below because both copies still end up
   exactly the same width as each other.
   Sized per project, not shared: Winterview's desktop screenshots render
   roughly 2.4x wider than Dyne's phone frames at the same card height, so the
   same px gap reads as tight on one and cavernous on the other. */
.work-flow-track img { width: 80cqw; height: 100%; flex: none; object-fit: contain; object-position: center; border-radius: 6px; }
.work-flow-winterview .work-flow-track img { margin-right: 22px; border-radius: 9px; }
/* Dyne's screens are portrait phones inside this landscape card. At the
   shared 80cqw slide width, object-fit: contain renders each phone at only
   ~23cqw (height-limited by its own aspect ratio), leaving so much empty
   backdrop either side of it that the next/previous phone never reaches the
   slide's edge to actually peek in. Sizing the slide to the phone's own
   rendered width instead removes that dead space, so neighbouring screens
   sit close enough to show a real sliver of themselves. */
.work-flow-dyne .work-flow-track img { width: auto; margin-right: 9px; }
.work-flow-winterview .work-flow-track { animation-name: workFlowStep3; animation-duration: 8s; }
.work-flow-dyne .work-flow-track { animation-name: workFlowStep4; animation-duration: 7s; }

/* Vignette: fades the peeking previous/next screen into the card's own tint at
   either edge, so whichever screen is centered at a given moment reads as the
   one in focus rather than several equally-weighted screens side by side. Pure
   position, not animation state, so it holds up regardless of where the track
   currently is. Extra stops ease the falloff (slow at first, faster through
   the middle of the fade, slow again into transparent) instead of a flat
   linear ramp, so the edge reads as a soft vignette rather than a hard line. */
.work-flow-dyne::after,
.work-flow-winterview::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 1;
}
.work-flow-dyne::after {
  background: linear-gradient(to right,
    oklch(0.55 0.18 25) 0%, oklch(0.55 0.18 25 / 0.85) 3%,
    oklch(0.55 0.18 25 / 0.45) 8%, oklch(0.55 0.18 25 / 0.12) 13%,
    oklch(0.55 0.18 25 / 0) 19%,
    oklch(0.55 0.18 25 / 0) 81%,
    oklch(0.55 0.18 25 / 0.12) 87%, oklch(0.55 0.18 25 / 0.45) 92%,
    oklch(0.55 0.18 25 / 0.85) 97%, oklch(0.55 0.18 25) 100%);
}
.work-flow-winterview::after {
  background: linear-gradient(to right,
    oklch(0.53 0.12 258) 0%, oklch(0.53 0.12 258 / 0.85) 3%,
    oklch(0.53 0.12 258 / 0.45) 8%, oklch(0.53 0.12 258 / 0.12) 13%,
    oklch(0.53 0.12 258 / 0) 19%,
    oklch(0.53 0.12 258 / 0) 81%,
    oklch(0.53 0.12 258 / 0.12) 87%, oklch(0.53 0.12 258 / 0.45) 92%,
    oklch(0.53 0.12 258 / 0.85) 97%, oklch(0.53 0.12 258) 100%);
}

/* A literal blur filter read as a glitch, not polish - dropped. Smoothness
   comes from the curve instead: a gentle, front-loaded deceleration into each
   arrival (steep at first, long soft tail) rather than a mechanical linear
   slide, so the stop feels settled instead of snapped. */
/* Winterview: the peek means every held frame shows a sliver of its
   neighbours, so a single trailing duplicate isn't enough any more - at the
   loop point the "next" and "previous" slivers would have nothing to show
   and the peek would flicker to empty once per cycle. The track instead
   holds the 3 real screens twice, rotated one step (download, improve,
   clarify, download, improve, clarify) so every one of the 4 held positions
   below (sixths of this 6-item track) has real neighbours on both sides,
   including the one the loop resets from - that frame and the first frame
   are pixel-identical, peek slivers included, so the reset is invisible. */
@keyframes workFlowStep3 {
  0%, 16%   { transform: translateX(calc(-16.667% + 10cqw)); }
  26%, 42%  { transform: translateX(calc(-33.333% + 10cqw)); animation-timing-function: cubic-bezier(.22, .61, .36, 1); }
  52%, 68%  { transform: translateX(calc(-50% + 10cqw)); animation-timing-function: cubic-bezier(.22, .61, .36, 1); }
  78%, 100% { transform: translateX(calc(-66.667% + 10cqw)); animation-timing-function: cubic-bezier(.22, .61, .36, 1); }
}
/* Dyne: same seamless-peek approach as Winterview, sized for 4 real screens.
   Track is [date, location, coupon, invite] repeated 3 times (12 items).
   Sizing each slide to the phone itself (rather than Winterview's fixed
   80cqw) packs enough screens into the card that 3-4 are visible at once,
   not just one neighbour peeking on either side - so a single repeat of the
   4-screen cycle isn't enough buffer any more. Holding on the middle
   repeat (indices 4-7: date, location, coupon, invite) leaves two full
   repeats of real screens as buffer on each side, so every simultaneously
   visible slot - however many that turns out to be at a given card width -
   is always a real, correctly-ordered screen instead of running out and
   popping in empty. Holds sit at twelfths of the track; the last hold
   (index 8, "date" again) is pixel-identical to the first (index 4), so the
   loop reset is invisible.
   Centering offset is half the leftover space in the slide's own container
   (100cqw - the phone's ~23cqw rendered width, halved) rather than Winterview's
   10cqw, since these slides are sized to the phone itself instead of a fixed
   80cqw. */
@keyframes workFlowStep4 {
  0%, 12%   { transform: translateX(calc(-33.333% + 38.5cqw)); }
  20%, 32%  { transform: translateX(calc(-41.667% + 38.5cqw)); animation-timing-function: cubic-bezier(.22, .61, .36, 1); }
  40%, 52%  { transform: translateX(calc(-50% + 38.5cqw)); animation-timing-function: cubic-bezier(.22, .61, .36, 1); }
  60%, 72%  { transform: translateX(calc(-58.333% + 38.5cqw)); animation-timing-function: cubic-bezier(.22, .61, .36, 1); }
  80%, 100% { transform: translateX(calc(-66.667% + 38.5cqw)); animation-timing-function: cubic-bezier(.22, .61, .36, 1); }
}
/* ---------- reveal, driven by scroll position ---------- */
/* The base rule above animates on load, which means anything below the fold has
   finished before it is ever seen. Where the browser supports a view timeline,
   drive each element from its own entry into the viewport instead. The stagger
   moves from animation-delay (meaningless on a scroll timeline) to a staggered
   animation-range, so the cards still cascade. */
@supports (animation-timeline: view()) {
  .section-title, .work-card, .about-quote, .about-body, .stat {
    animation-timeline: view();
    animation-range: entry 5% cover 30%;
    animation-delay: 0s;
  }
  .work-card:nth-child(1) { animation-range: entry 5% cover 26%; }
  .work-card:nth-child(2) { animation-range: entry 9% cover 30%; }
  .work-card:nth-child(3) { animation-range: entry 13% cover 34%; }
  .stat:nth-of-type(1) { animation-range: entry 5% cover 24%; }
  .stat:nth-of-type(2) { animation-range: entry 8% cover 27%; }
  .stat:nth-of-type(3) { animation-range: entry 11% cover 30%; }
  .stat:nth-of-type(4) { animation-range: entry 14% cover 33%; }
}

/* ---------- work card hover ---------- */
/* The preview already clips, so the screenshot can drift under it. */
.work-preview img { transition: transform 0.45s cubic-bezier(.2, .7, .3, 1); }
.work-card:hover .work-preview img,
.work-card:focus-visible .work-preview img { transform: scale(1.04); }
/* The filmstrip pauses on hover instead of zooming - a per-image scale would
   pull the two flex-adjacent copies out of alignment and show a seam. */
.work-card:hover .work-flow-track img,
.work-card:focus-visible .work-flow-track img { transform: none; }

/* ---------- page transitions ---------- */
/* Tried naming the card image and the case-study banner the same, so the browser
   would morph one into the other across the navigation. Removed: the card crops
   to a 16:9 box via object-fit:cover, the banner shows the full image at its own
   aspect ratio (1.55:1 / 2.08:1, neither close to 16:9) with no crop at all -
   morphing a small cropped sliver into a large uncropped rectangle of a different
   shape looked warped rather than smooth. The whole-page crossfade from
   base.css's @view-transition rule still applies; only the per-image morph is gone. */
/* ---------- reduce motion: the video ---------- */
/* Collapsing animation-duration cannot pause a <video>, so the largest source
   of motion on the page ignored the toggle. Hiding it falls back to the poster
   frame already painted behind it, and keeps this page script-free. */
.motion-toggle-input:checked ~ .page-body .hero-skyline video { display: none; }
@media (prefers-reduced-motion: reduce) {
  .hero-skyline video { display: none; }
}
