:root {
  /* colour (placeholder palette) */
  --bg: #f7f7f7;        /* page: neutral near-white (no warm tint), so white cards still lift */
  --surface: #ffffff;   /* cards */
  --chip: #f2f2f2;      /* tag pills on a card — neutral grey */
  --stack: #000000;      /* the black card behind a project card */
  --on-stack: #ffffff;
  --on-stack-muted: rgb(255 255 255 / .6);

  /* hero sky — sampled from Jacob's reference (2026-10-03); deep at the top, lifting toward the fade */
  --sky-top: #034da5;
  --sky-mid: #1e74d4;    /* behind the subtext: still dark enough for white body text */
  --sky-low: #89c0f5;
  --sky-pale: #d6e9fc;   /* last tint before the page colour */
  --sky-glow: ellipse 150% 75% at 50% 100%;  /* curved horizon glow rising from the bottom centre */
  --grain: .4;           /* noise strength (0–1) */
  --on-sky: #ffffff;
  --on-sky-muted: rgb(255 255 255 / .86);
  --text: #1a1a1a;
  --muted: #6b6b6b;
  --line: #e6e6e6;
  --accent: #1a1a1a;
  --nav: #000000;        /* nav links */

  /* type */
  --font-body: system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-display: var(--font-body);   /* one sans family site-wide (serif Newsreader dropped 2026-10-04) */
  /* Type scale: Apple HIG text styles (iOS, Large/default), with each leading rounded to the 4pt grid.
     HIG size/leading → ours:  Large Title 34/41 → 34/40 · Title 1 28/34 → 28/36 · Title 2 22/28 → 22/28
     Title 3 20/25 → 20/24 · Body 17/22 → 17/24 · Callout 16/21 → 16/20 · Subhead 15/20 → 15/20
     Footnote 13/18 → 13/20 · Caption 1 12/16 → 12/16
     The web hero goes past Large Title on bigger screens (Display), still on the grid. */
  --text-display-l: 48px; --leading-display-l: 56px;
  --text-display: 40px;   --leading-display: 48px;
  --text-large-title: 34px; --leading-large-title: 40px;
  --text-title1: 28px;  --leading-title1: 36px;
  --text-title2: 22px;  --leading-title2: 28px;
  --text-title3: 20px;  --leading-title3: 24px;
  --text-body: 17px;    --leading-body: 24px;
  --text-callout: 16px; --leading-callout: 20px;
  --text-subhead: 15px; --leading-subhead: 20px;
  --text-footnote: 13px; --leading-footnote: 20px;
  --text-caption: 12px; --leading-caption: 16px;
  /* aliases used by components */
  --text-sm: var(--text-footnote); --leading-sm: var(--leading-footnote);
  --text-h2: var(--text-title1);   --leading-h2: var(--leading-title1);
  --text-h3: var(--text-title2);   --leading-h3: var(--leading-title2);

  /* spacing — 4pt grid */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;
  --space-24: 96px;
  --space-32: 128px;
  --space-40: 160px;
  --space-60: 240px;
  --space-64: 256px;
  --space-72: 288px;
  --space-80: 320px;

  /* layout — stepped, not fluid, so they stay on the grid */
  --gutter: var(--space-4);          /* page side padding */
  --section-gap: var(--space-16);    /* between major sections */
  --container: 1120px;  /* site width: header, hero card, work grid and footer all share these edges (as on maureen.care) */
  --max: 800px;         /* reading measure: long-form text such as case studies */
  /* card shape — generous and stepped per breakpoint, like maureen.care's cards */
  --radius-card: 32px;
  --pad-card: var(--space-6);
}
@media (min-width: 600px) { :root { --gutter: var(--space-6); --section-gap: var(--space-20); --radius-card: 40px; --pad-card: var(--space-8); } }
@media (min-width: 900px) { :root { --gutter: var(--space-8); --section-gap: var(--space-24); --radius-card: 48px; --pad-card: var(--space-10); } }
@media (prefers-color-scheme: dark) {
  :root { --bg: #111; --surface: #1c1c1c; --chip: #2a2a2a; --text: #eee; --muted: #999; --line: #2a2a2a; --accent: #eee; --nav: #ffffff;
          --sky-top: #021f45; --sky-mid: #0a3a78; --sky-low: #12305a; --sky-pale: #141c28; }
  /* dark mode: only the resting fill changes, to the Instagram button's grey (the near-black --surface barely read); hover still inverts as normal */
  .site-header .icon-link, .contact-pill .contact-pill-icon { background: var(--line); }   /* the two mail circles; two-class selectors beat the later base rules and lose to their :hover */
}

/* base */
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font-family: var(--font-body); font-size: var(--text-body); line-height: var(--leading-body);
}
h1, h2, h3, p, dl, dd { margin: 0; }   /* no browser-default margins: spacing comes from tokens only */
a { color: inherit; }
h1, h2, h3 { font-family: var(--font-display); font-optical-sizing: auto; font-weight: 500; }
h2 { font-size: var(--text-h2); line-height: var(--leading-h2); font-weight: 400; }  /* softer than the hero's 500 */
h3 { font-size: var(--text-h3); line-height: var(--leading-h3); }

/* layout */
.site-header, .site-footer, main { max-width: var(--container); margin: 0 auto; padding: var(--space-6) var(--gutter); }
main.case-study { max-width: var(--max); }
.site-header {
  display: flex; justify-content: space-between; align-items: center; gap: var(--space-4);  /* centred: top-aligning read too high (tried 2026-10-04) */
  padding-block: var(--space-4);   /* 16 + 40px signature + 16 = 72px, the same header height as before */
}
/* Fixed header (pattern from maureen.care): stays put while the page scrolls under a soft blur band.
   nav-tone.js sets html.tone-light / html.tone-dark from whatever is under the signature, and the
   signature and the band's tint follow, so the signature always reads. */
.site-header { position: fixed; inset: 0 0 auto; z-index: 100; }
body { padding-top: var(--header-h); }   /* the header no longer takes up room in the flow */
.site-header::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  top: 0; left: calc(50% - 50vw); width: 100vw; height: calc(100% + var(--space-3));   /* full bleed, a little past the header */
  background-color: rgb(247 247 247 / .3);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgb(0 0 0 / .9) 25%, rgb(0 0 0 / .6) 55%, rgb(0 0 0 / .25) 80%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, rgb(0 0 0 / .9) 25%, rgb(0 0 0 / .6) 55%, rgb(0 0 0 / .25) 80%, transparent 100%);
  transition: background-color .35s ease;
}
html.tone-dark .site-header::before { background-color: rgb(0 0 0 / .14); }
/* Jacob's signature, top left. One vector file (the same SVG as the footer) used as a MASK and filled with --nav, so it recolours from
   a token (black on the page, white in dark mode) with no edge residue. Master: portfolio/_source/signature-*.png */
.signature {
  --sig-h: var(--space-10);   /* 40px tall */
  display: block; flex: none;
  height: var(--sig-h); width: calc(var(--sig-h) * 273.88 / 116.18);   /* the SVG's viewBox */
  translate: 0 6px;   /* optical nudge down (an artwork offset, so off-grid by design): the long J and g tails make it look higher */
  background-color: var(--nav);   /* before the script runs: black on the page, white in dark mode */
  transition: background-color .35s ease;
  -webkit-mask: url("/assets/img/signature.svg") center / contain no-repeat;
          mask: url("/assets/img/signature.svg") center / contain no-repeat;
}
html.tone-light .signature { background-color: #000; }   /* over light backgrounds */
html.tone-dark .signature { background-color: #fff; }    /* over the sky, the black panel, dark mode */
.site-header nav { display: flex; gap: var(--space-4); }
.site-header nav a { text-decoration: none; color: var(--nav); }
/* icons are inline SVG filled with currentColor, so their colour comes from the link's token — no separate white/black files */
.icon { display: block; width: var(--space-6); height: var(--space-6); }
.icon-link {
  display: grid; place-items: center; width: var(--space-10); height: var(--space-10);  /* 40px circle = tap target */
  border-radius: 999px; background: var(--surface); color: var(--nav);   /* white circle, black icon — the same wherever it appears */
}
.icon-link .icon { width: var(--space-5); height: var(--space-5); }   /* 20px icon inside the 40px circle */
.icon-link { transition: background-color 200ms, color 200ms; }
.icon-link:hover, .icon-link:focus-visible { background: var(--nav); color: var(--surface); }   /* inverts on hover: clearly a button */
main > section + section { margin-top: var(--section-gap); }
/* mail button below the footer card, at the very bottom of the page, right-aligned on the content edge */
/* a quieter circle for secondary links (Instagram): neutral grey fill, muted glyph, so the Contact pill keeps the weight */
.icon-link-quiet { background: var(--line); color: var(--muted); }
.icon-link-quiet:hover, .icon-link-quiet:focus-visible { background: color-mix(in oklab, var(--line), var(--text) 8%); color: var(--text); }   /* circle dims a touch, glyph darkens */
/* Instagram circle on the left content edge, Contact pill on the right */
.footer-contact { display: flex; justify-content: space-between; align-items: center; max-width: var(--container); margin: 0 auto; padding: var(--space-6) var(--gutter); }

/* contact pill: a longer black pill with the word "Contact", holding the mail icon in a circle styled exactly like the nav
   mail button (white circle, black glyph), so the two mail buttons always match. Everything inverts on hover. */
.contact-pill {
  display: inline-flex; align-items: center; gap: var(--space-3);
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-5);   /* a 4px gap rings the 40px circle at the pill's right end */
  border-radius: 999px; background: var(--nav); color: var(--surface);
  font-weight: 600; text-decoration: none;
  transition: background-color 200ms, color 200ms;
}
.contact-pill-icon {
  display: grid; place-items: center; width: var(--space-10); height: var(--space-10);
  border-radius: 999px; background: var(--surface); color: var(--nav);
  transition: background-color 200ms, color 200ms;
}
.contact-pill-icon .icon { width: var(--space-5); height: var(--space-5); }
.contact-pill:hover, .contact-pill:focus-visible { background: var(--surface); color: var(--nav); }
.contact-pill:hover .contact-pill-icon, .contact-pill:focus-visible .contact-pill-icon { background: var(--nav); color: var(--surface); }

/* footer — the hero card in reverse: a shorter rounded card whose sky rises out of the page (eased fade at the
   top, rounded bottom corners, same insets as the hero). The signature sits on it in white; the mail button sits below it on the page. */
.site-footer {
  --foot-fade: var(--sky-fade);   /* the hero's fade length (up to 384px), so both ends of the page dissolve alike */
  position: relative; isolation: isolate;
  max-width: none; margin: var(--section-gap) var(--hero-inset) 0;
  display: flex; flex-direction: column; align-items: flex-end;
  /* % here is the page width (the footer's containing block), so subtract the card's own inset to land on the content edges */
  padding: var(--foot-fade) max(var(--hero-pad), calc((100% - var(--container)) / 2 + var(--gutter) - var(--hero-inset))) var(--space-12);
  color: var(--on-sky);
}
.site-footer::before, .site-footer::after { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: 0 0 var(--hero-radius) var(--hero-radius); pointer-events: none; }
.site-footer::before {
  /* the lighter end of the sky palette, eased in from the page colour at the top: the hero's eleven-stop
     smoothstep fade, mirrored, over the same length, so no start or end line shows */
  background: linear-gradient(to top, var(--sky-mid) 0%,
    color-mix(in oklab, var(--bg) 0%, var(--sky-mid)) calc(100% - var(--foot-fade) * 1.00),
    color-mix(in oklab, var(--bg) 3%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.90),
    color-mix(in oklab, var(--bg) 10%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.80),
    color-mix(in oklab, var(--bg) 22%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.70),
    color-mix(in oklab, var(--bg) 35%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.60),
    color-mix(in oklab, var(--bg) 50%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.50),
    color-mix(in oklab, var(--bg) 65%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.40),
    color-mix(in oklab, var(--bg) 78%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.30),
    color-mix(in oklab, var(--bg) 90%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.20),
    color-mix(in oklab, var(--bg) 97%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.10),
    var(--bg) 100%);
  background: linear-gradient(to top in oklab, var(--sky-mid) 0%,
    color-mix(in oklab, var(--bg) 0%, var(--sky-mid)) calc(100% - var(--foot-fade) * 1.00),
    color-mix(in oklab, var(--bg) 3%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.90),
    color-mix(in oklab, var(--bg) 10%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.80),
    color-mix(in oklab, var(--bg) 22%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.70),
    color-mix(in oklab, var(--bg) 35%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.60),
    color-mix(in oklab, var(--bg) 50%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.50),
    color-mix(in oklab, var(--bg) 65%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.40),
    color-mix(in oklab, var(--bg) 78%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.30),
    color-mix(in oklab, var(--bg) 90%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.20),
    color-mix(in oklab, var(--bg) 97%, var(--sky-mid)) calc(100% - var(--foot-fade) * 0.10),
    var(--bg) 100%);
}
.site-footer::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: var(--grain); mix-blend-mode: overlay;
  -webkit-mask-image: linear-gradient(to top, #000 calc(100% - var(--foot-fade)), transparent 100%);
          mask-image: linear-gradient(to top, #000 calc(100% - var(--foot-fade)), transparent 100%);
}
/* the pilot emoji, centred and standing on the footer card's bottom edge (decorative) */
.footer-pilot {
  position: absolute; left: 50%; bottom: 0; translate: -50% 0;   /* no vertical nudge: at line-height 1 the feet already rest on the edge */
  font-size: var(--space-12); line-height: 1;   /* 48px */
}
/* the signature as a sign-off: vector, spanning the full content width, recoloured by token (mask + --on-sky) */
.footer-signature {
  display: block; width: 100%; aspect-ratio: 273.88 / 116.18;   /* the SVG's viewBox */
  margin-top: var(--space-12);
  background-color: var(--on-sky);   /* white on the footer sky */
  -webkit-mask: url("/assets/img/signature.svg") center / contain no-repeat;
          mask: url("/assets/img/signature.svg") center / contain no-repeat;
}
.site-footer a { text-decoration: none; text-underline-offset: .2em; }
.site-footer a:hover, .site-footer a:focus-visible { text-decoration: underline; }   /* underline only on hover */

/* hero — a rounded card holding a CSS sky (gradient + SVG grain, no image). Text on it is white.
   Two layers: .hero-sky is clipped to the card's rounded shape; .flight is NOT, so the plane takes off
   inside the card and escapes past its right edge over the page. The nav sits above the card on --bg.
   Only the TOP corners are rounded; the bottom dissolves into the page with the eased fade.
   Phones: no side margins. 600px+: inset by the gutter. */
:root {
  --hero-inset: 0px;
  /* dramatic and fluid: grows with the screen, from 40px on phones to 96px on wide screens */
  --hero-radius: clamp(40px, 6.5vw, 96px);
  --hero-radius: clamp(40px, round(6.5vw, 4px), 96px);   /* snapped to 4px where round() is supported */
  --sky-fade: min(384px, 40svh);  /* bottom stretch where the sky dissolves into --bg (long = soft) */
  --sky-fade: min(384px, round(down, 40svh, 4px));
  --header-h: calc(var(--space-4) * 2 + var(--space-10));   /* header: 16px padding top and bottom + the 40px signature */
  --hero-pad: var(--gutter);      /* text inset inside the card */
}
@media (min-width: 600px) { :root { --hero-inset: var(--gutter); } }
@media (min-width: 900px) { :root { --hero-pad: var(--space-16); } }

html, body { overflow-x: clip; }   /* the escaping plane must never cause sideways scrolling */
.hero {
  position: relative; isolation: isolate; color: var(--on-sky);
  display: flex; flex-direction: column;
  margin: 0 var(--hero-inset);   /* the card stays wide: page gutter on each side, full width on phones */
  min-height: calc(100vh - var(--header-h) - var(--hero-inset));
  min-height: calc(100svh - var(--header-h) - var(--hero-inset));   /* fills the screen below the nav */
  padding-top: var(--space-16);
  padding-bottom: var(--sky-fade);   /* keeps the text above the fade */
}
.hero .intro {
  width: 100%; margin-block: auto;   /* centred vertically in the card */
  /* text lines up with the content edges (signature, nav, Work) on wide screens; never closer than --hero-pad to the card edge */
  padding-inline: max(var(--hero-pad), calc((100% - var(--container)) / 2 + var(--gutter)));
}
.hero-sky {
  position: absolute; inset: 0; z-index: -1; overflow: hidden;
  border-radius: var(--hero-radius) var(--hero-radius) 0 0;
}
.hero-sky::before, .hero-sky::after { content: ""; position: absolute; inset: 0; pointer-events: none; }
.hero-sky::before {
  --glow-strength: 30%;  /* how much the curved glow shows over the base blue */
  --glow-stops:
    color-mix(in oklab, var(--sky-pale) var(--glow-strength), transparent) 0%,
    color-mix(in oklab, var(--sky-low) var(--glow-strength), transparent) 36%,
    transparent 72%;
  /* eased fade into the page: eleven stops on a smoothstep curve, mixed in oklab — no visible start or end line */
  --sky-stops:
    var(--sky-top) 0%,
    color-mix(in oklab, var(--bg) 0%, var(--sky-mid)) calc(100% - var(--sky-fade) * 1.00),
    color-mix(in oklab, var(--bg) 3%, var(--sky-mid)) calc(100% - var(--sky-fade) * 0.90),
    color-mix(in oklab, var(--bg) 10%, var(--sky-mid)) calc(100% - var(--sky-fade) * 0.80),
    color-mix(in oklab, var(--bg) 22%, var(--sky-mid)) calc(100% - var(--sky-fade) * 0.70),
    color-mix(in oklab, var(--bg) 35%, var(--sky-mid)) calc(100% - var(--sky-fade) * 0.60),
    color-mix(in oklab, var(--bg) 50%, var(--sky-mid)) calc(100% - var(--sky-fade) * 0.50),
    color-mix(in oklab, var(--bg) 65%, var(--sky-mid)) calc(100% - var(--sky-fade) * 0.40),
    color-mix(in oklab, var(--bg) 78%, var(--sky-mid)) calc(100% - var(--sky-fade) * 0.30),
    color-mix(in oklab, var(--bg) 90%, var(--sky-mid)) calc(100% - var(--sky-fade) * 0.20),
    color-mix(in oklab, var(--bg) 97%, var(--sky-mid)) calc(100% - var(--sky-fade) * 0.10),
    color-mix(in oklab, var(--bg) 100%, var(--sky-mid)) 100%;
  /* vertical depth + a subtle curved glow rising from the bottom centre */
  background:
    radial-gradient(var(--sky-glow), var(--glow-stops)),
    linear-gradient(to bottom, var(--sky-stops));
  background:
    radial-gradient(var(--sky-glow) in oklab, var(--glow-stops)),
    linear-gradient(to bottom in oklab, var(--sky-stops));
  /* the glow fades out before the bottom edge so it never tints the page */
  --glow-fade: linear-gradient(to bottom, #000 calc(100% - var(--sky-fade) * .4),
    rgb(0 0 0 / .85) calc(100% - var(--sky-fade) * .3), rgb(0 0 0 / .5) calc(100% - var(--sky-fade) * .2),
    rgb(0 0 0 / .15) calc(100% - var(--sky-fade) * .1), transparent 100%);
  -webkit-mask-image: var(--glow-fade);
          mask-image: var(--glow-fade);
}
.hero-sky::after {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - var(--sky-fade)), transparent calc(100% - var(--sky-fade) * .3));
          mask-image: linear-gradient(to bottom, #000 calc(100% - var(--sky-fade)), transparent calc(100% - var(--sky-fade) * .3));
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: var(--grain);
  mix-blend-mode: overlay;
}
.hero + main { padding-top: var(--space-6); }  /* the sky has fully dissolved by the hero's bottom edge */

/* flight — a Cessna towing a banner across the sky, behind the text.
   Plane art: assets/img/plane-*.webp (masters in portfolio/_source/). Both images share one canvas,
   so the propeller sits on the nose by itself; it sits behind the body so the spinner hides the root. */
:root {
  --plane-w: clamp(120px, 18vw, 224px);
  --plane-h: calc(var(--plane-w) * 273 / 677);
  --flight-start: 64%;   /* starting height in the hero: below the intro text */
  --climb: 8deg;         /* nose-up angle of the whole plane + banner */
  --climb-slope: .1405;  /* tan(--climb): how much it rises per unit across — keep the two in step */
  --flight-dur: 24s;     /* one crossing */
}
.hero .intro { position: relative; z-index: 1; }
.flight {
  position: absolute; z-index: 2; inset: 0;   /* above the text and the card edge, so it visibly breaks out */
  /* clip only the LEFT: the plane appears from the card's left edge, but is free to leave past the right */
  clip-path: inset(-100vh -100vw -100vh 0);
  pointer-events: none;
}
/* decorative only: no link, no hover, hidden from screen readers */
.flight-path {
  position: absolute; left: 0; top: var(--flight-start);
  display: block;
  animation: fly var(--flight-dur) linear 1s infinite both;
}
.flight-bob { display: flex; align-items: flex-start; animation: bob 4s ease-in-out infinite alternate; }
/* climbs from the lower left to the upper right, nose up, like a takeoff */
@keyframes fly {
  from { transform: translate(-100%, 0) rotate(calc(var(--climb) * -1)); }
  to   { transform: translate(100vw, calc(100vw * var(--climb-slope) * -1)) rotate(calc(var(--climb) * -1)); }
}
@keyframes bob { from { transform: translateY(calc(var(--space-1) * -1)); } to { transform: translateY(var(--space-1)); } }

/* tow line meets the tail tie-down: 6.8% in from the canvas's left edge, 65.9% down (measured on the art) */
.tow {
  --banner-h: calc(var(--leading-sm) + 2 * var(--space-1));   /* 28px: footnote leading + 4px padding top and bottom */
  display: flex; align-items: center; height: var(--banner-h);
  margin-top: calc(var(--plane-h) * .659 - var(--banner-h) / 2); /* centre the line on the attach point */
  margin-right: calc(var(--plane-w) * -.068);                  /* run the line under the tail to the attach point */
}
.banner {
  padding: var(--space-1) var(--space-3);
  background: var(--surface); color: var(--text);
  border-radius: 2px; box-shadow: inset 0 0 0 1px rgb(0 0 0 / .08);  /* hairline so it still reads once it's over the page */
  font: 600 var(--text-sm) / var(--leading-sm) var(--font-body);
  text-transform: uppercase; letter-spacing: .06em; white-space: nowrap;
}
.rope { width: calc(var(--space-12) + var(--plane-w) * .068); height: 1px; background: rgb(0 0 0 / .35); }  /* reads on the sky and on the page */
.plane { position: relative; z-index: 1; display: block; flex: none; width: var(--plane-w); aspect-ratio: 677 / 273; }
.plane img { position: absolute; inset: 0; width: 100%; height: 100%; }
.plane-prop {
  transform-origin: 95.1% 52%;   /* the propeller hub, measured on the 677×273 canvas */
  animation: prop 80ms linear infinite;  /* seen side-on, a spinning prop flickers rather than rotates */
}
@keyframes prop { 0% { transform: scaleY(1); } 50% { transform: scaleY(-1); } 100% { transform: scaleY(1); } }

@media (prefers-reduced-motion: reduce) {
  /* parked in the sky: no flight, no bob, no flicker */
  .flight-path { animation: none; left: auto; right: var(--gutter); transform: rotate(calc(var(--climb) * -1)); }
  .flight-bob, .plane-prop { animation: none; }
}

/* intro — two lines: Large Title on phones, stepping up to Display sizes on wider screens. */
.intro h1 {
  display: flex; flex-direction: column;
  font-size: var(--text-large-title); line-height: var(--leading-large-title);
}
/* Steps, not fluid, so size and leading stay on the scale and the grid.
   The longer line ("I pilot ideas from sketch to takeoff") is ~14.8em: it is kept whole only where it fits. */
@media (min-width: 600px)  { .intro-line { white-space: nowrap; } }                       /* 34px × 14.8 ≈ 503 ≤ 552 */
@media (min-width: 768px)  { .intro h1 { font-size: var(--text-display);   line-height: var(--leading-display); } }    /* 40 → ≈ 592 ≤ 720 */
@media (min-width: 1024px) { .intro h1 { font-size: var(--text-display-l); line-height: var(--leading-display-l); } }  /* 48 → ≈ 710 ≤ 736 */
.intro p { margin-top: var(--space-6); font-size: var(--text-body); line-height: var(--leading-body); color: var(--on-sky-muted); max-width: 60ch; text-wrap: balance; }  /* balance: when it wraps, lines come out even instead of leaving one word hanging */

/* work — heading sits tight to its cards, far from the hero */
.work h2 { margin-bottom: var(--space-4); }
/* one project per row; on desktop each project is a card stack (white card + black details card) */
.work-grid { display: grid; gap: var(--space-6); }
.card {
  display: block; padding: var(--pad-card);
  overflow: hidden;   /* clips the centred phone image's empty side and the hover tint to the card's corners */
  background: var(--surface); border-radius: var(--radius-card);
  text-decoration: none;
}
.card h3 + p { margin-top: var(--space-2); }   /* 8px from the project title to its description */
/* Hover: the image area's colour tint (below) is the only hover signal — no lift, no shadow. */

/* card media — the whole phone above the title; only the bottom of the image (the hand below the phone)
   dissolves into the card. The fade is an eased mask, so it blends into whatever is behind it. */
.card-media {
  --phone-w: var(--space-64);
  position: relative; isolation: isolate;
  display: flex; justify-content: center;
  margin-bottom: var(--space-6);
}
/* Hover tint: the project's colour fills the image area out to the card edges and dissolves downward
   with the same eased fade as the phone. Each card sets --card-brand (top) and optionally --card-brand-light
   (bottom); without the second, the top colour is lightened toward the surface. */
.card-media::before {
  content: ""; position: absolute; z-index: -1;
  inset: calc(var(--pad-card) * -1) calc(var(--pad-card) * -1) 0;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  /* the project colour at the top, lightening toward the bottom, then dissolving with the fade mask */
  background: linear-gradient(to bottom in oklab,
    var(--card-brand, var(--chip)),
    var(--card-brand-light, color-mix(in oklab, var(--card-brand, var(--chip)) 45%, var(--surface))));
  -webkit-mask-image: var(--media-fade);
          mask-image: var(--media-fade);
  opacity: 0;
  transition: opacity 300ms cubic-bezier(.22, 1, .36, 1);
}
.card:hover .card-media::before, .card:focus-visible .card-media::before { opacity: 1; }
.card-media img {
  width: var(--phone-w); height: auto;
  translate: var(--media-shift, 0) 0;   /* centre the PHONE, not the image (the hand enters from the left) */
  -webkit-mask-image: var(--media-fade);
          mask-image: var(--media-fade);
}
.card-media {
  --media-fade: linear-gradient(to bottom, #000 64%,
    rgb(0 0 0 / .9) 70%, rgb(0 0 0 / .7) 76%, rgb(0 0 0 / .45) 82%,
    rgb(0 0 0 / .2) 88%, rgb(0 0 0 / .06) 94%, transparent 100%);
}
@media (min-width: 600px) { .card-media { --phone-w: var(--space-80); } }

/* Card stack: the white project card rests on a black details card.
   Desktop (900px+): side by side, the black card to the right. Phones and tablets: stacked, the black card below.
   Either way the black card starts UNDER the white card (by one radius), so black shows only in the white card's
   corner curves on the joining side, never along a shared edge where the curve would leave a dark anti-aliased fringe. */
.card-stack { display: grid; }
.card-stack .card { position: relative; z-index: 1; }
.card-details {
  position: relative; display: flex; flex-direction: column;
  background: var(--stack); color: var(--on-stack); text-decoration: none;   /* the whole black panel is a link to the case study */
  /* phones: below the white card */
  border-radius: 0 0 var(--radius-card) var(--radius-card);
  margin-top: calc(var(--radius-card) * -1);
  padding: var(--pad-card); padding-top: calc(var(--pad-card) + var(--radius-card));
}
/* one column of rows divided by hairlines */
.card-details dl { flex: 1; display: flex; flex-direction: column; justify-content: space-between; margin: 0; }
.card-details dl > div { padding-block: var(--space-4); border-top: 1px solid rgb(255 255 255 / .16); }
.card-details dl > div:first-child { border-top: 0; padding-top: 0; }
.card-details dl > div:last-child { padding-bottom: 0; }
.card-details dd { margin: var(--space-1) 0 0; }
.card-details dt { font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--on-stack-muted); }
.card-details dd { font-size: var(--text-body); line-height: var(--leading-body); }
/* a second way in: a white circle with a ↗ arrow on its own row at the panel's bottom right, clear of the rows' text.
   Matches the mail button. */
.arrow-link {
  flex: none; align-self: flex-end; margin-top: var(--space-6);
  width: var(--space-12); height: var(--space-12);   /* 48px */
  background: var(--surface); color: var(--nav);
}
.arrow-link .icon { width: var(--space-5); height: var(--space-5); rotate: -45deg; }   /* → becomes ↗ */
/* hovering anywhere on the stack fills the arrow and tints the white card's image area, so the unit reacts as one */
.card-stack:hover .arrow-link, .card-stack:focus-within .arrow-link { background: var(--card-brand, var(--nav)); color: var(--on-stack); }
.card-stack:hover .card-media::before { opacity: 1; }
@media (min-width: 900px) {
  .card-stack { grid-template-columns: repeat(2, minmax(0, 1fr)); }   /* white card keeps the half width it had before */
  .card-details {
    border-radius: 0 var(--radius-card) var(--radius-card) 0;
    margin-top: 0; margin-left: calc(var(--radius-card) * -1);
    padding: var(--pad-card); padding-left: calc(var(--pad-card) + var(--radius-card));
  }
  .card-details dd { font-size: var(--text-title3); line-height: var(--leading-title3); }
}

/* tags — neutral pills under a card's description */
.tags { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-4) 0 0; padding: 0; list-style: none; }
.tags li {
  padding: var(--space-1) var(--space-3); border-radius: 999px;
  background: var(--chip); color: var(--muted);
  font-size: var(--text-sm); line-height: var(--leading-sm);
}

/* case study */
.cs-header h1 { font-size: var(--text-large-title); line-height: var(--leading-large-title); }
.cs-header .tags { margin-top: var(--space-4); }
@media (min-width: 768px) { .cs-header h1 { font-size: var(--text-display); line-height: var(--leading-display); } }
.cs-meta {   /* Role, Timeline, Status in a white card, matching the project cards */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-6);
  margin: var(--space-8) 0 0; padding: var(--pad-card);
  background: var(--surface); border-radius: var(--radius-card);
}
.cs-meta dd { margin: var(--space-1) 0 0; }
.cs-header + section { margin-top: var(--section-gap); }   /* the same gap as between sections, so the meta card doesn't crowd the first heading */
.cs-meta dt { font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted); }
.case-study section h2 { margin-bottom: var(--space-4); }

/* utilities */
/* word rotator (Work heading): every word stacked in ONE grid cell, so the slot is as wide as the longest word and
   the line never shifts. Motion measured on muse.ai: a short drift (0.3em) paired with a fade, and NO clipping,
   so the fade does the hiding and there is never a hard edge. Old word: up and out, quick (150ms, ease-in).
   New word: up from below into place, softer (260ms, ease-out), starting before the old one has fully gone. */
.rotator { display: inline-grid; vertical-align: bottom; }
.rotator-word {
  grid-area: 1 / 1; white-space: nowrap;
  opacity: 0; translate: 0 .3em;   /* waiting below, invisible */
  transition: none;
}
.rotator-word.is-on  { opacity: 1; translate: 0 0; transition: opacity 260ms ease-out 100ms, translate 260ms cubic-bezier(.2, .8, .2, 1) 100ms; }
.rotator-word.is-off { opacity: 0; translate: 0 -.3em; transition: opacity 150ms ease-in, translate 150ms ease-in; }
@media (prefers-reduced-motion: reduce) { .rotator-word, .rotator-word.is-on, .rotator-word.is-off { transition: none; translate: none; } }
.keep { white-space: nowrap; }   /* phrase that must never split across lines, e.g. "sketch to takeoff" */
.phone-only { display: none; }
@media (max-width: 599px) { br.phone-only { display: inline; } }   /* a line break used on phones only, e.g. after "Founder," */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
