/* ============================================================
   Project page template
   Edit the tokens below to rebrand the whole page.
   ============================================================ */

:root {
  --bg:          #ffffff;
  --bg-alt:      #f5f5f3;
  --ink:         #111111;
  --ink-soft:    #5b5b5b;
  --rule:        #e4e4e0;
  --accent:      #111111;

  --font:        "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
                 Helvetica, Arial, sans-serif;

  --wrap:        1280px;
  --wrap-narrow: 760px;
  --gutter:      clamp(1.25rem, 4vw, 3rem);
  --section:     clamp(4rem, 9vw, 8rem);
  --radius:      2px;
  --nav-h:       76px;
}

/* Light-only by design: the hero is a bright studio plate and the
   tool screenshots are light UI, so a dark shell fights both. */

/* ── Base ─────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img, iframe { max-width: 100%; display: block; }
img { height: auto; }

a { color: inherit; text-decoration: none; }

h1, h2, h3 {
  margin: 0;
  line-height: 1.08;
  letter-spacing: -0.03em;
  font-weight: 600;
}

/* ── Layout ───────────────────────────────────────────── */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: var(--wrap-narrow); }

.section        { padding-block: var(--section); }
.section--tight { padding-block: calc(var(--section) * 0.45); }

/* Anchor targets stop below the sticky bar instead of under it. body is
   excluded: it carries id="top", which must land at the true page top
   rather than a nav-height below it. */
:not(body)[id] { scroll-margin-top: var(--nav-h); }

/* #cinematic is a full-height band with the video centred in it, so it
   wants the section top flush with the viewport top, not pushed down
   by the nav, which would leave the video sitting low. The bar is
   translucent and the band's top is black, so nothing is obscured. */
.section--full { scroll-margin-top: 0; }

/* Dark band for a single section, so the media reads as lit rather than
   sitting on paper. Full-bleed by design; the .wrap inside still holds
   the content to the page grid. Tune the two ends via the tokens. */
:root {
  --band-top: #000000;   /* under the nav        */
  --band-bot: #2a2a2a;   /* at the section's end */
}

.section--dark {
  /* oklab keeps the ramp perceptually even. Plain sRGB interpolation
     crushes the dark end and bands visibly across a tall section. */
  background: linear-gradient(
    to bottom in oklab,
    var(--band-top) 0%,
    var(--band-bot) 100%
  );
  color: #fff;
}

/* Older engines ignore the `in oklab` syntax above and would drop the
   whole declaration, leaving the section transparent. */
@supports not (background: linear-gradient(to bottom in oklab, #000, #fff)) {
  .section--dark {
    background: linear-gradient(to bottom,
                var(--band-top), var(--band-bot));
  }
}
/* The video's own placeholder fill would flash light-grey before the
   first frame paints. Sit it between the two ends so it disappears
   into the gradient rather than reading as a block on top of it. */
.section--dark .video {
  background: color-mix(in oklab, var(--band-top), var(--band-bot));
}
.section--dark figcaption { color: rgba(255, 255, 255, 0.62); }

/* A .chapter draws a light divider on top. Following a dark band that
   reads as a stray white strip. The band edge is already the break. */
.section--dark + .chapter { border-top: 0; }

/* Fill the screen and centre the content, so scrolling the video into
   place leaves nothing but black around it. min-height (not height) so
   tall content still grows the band instead of overflowing it.
   svh tracks mobile browsers' collapsing toolbars; vh is the fallback. */
.section--full {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* The section's own padding is what stops the media touching the screen
   edges once it's centred; below this width the fixed viewport height
   squeezes it, so let the band grow with the content instead. */
@media (max-width: 700px) {
  .section--full { min-height: 0; display: block; }
}

/* ── Nav ──────────────────────────────────────────────── */

.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  /* 25% transparent = 75% opaque. Blur keeps links legible over
     whatever scrolls beneath. */
  background: color-mix(in srgb, var(--bg) 75%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--rule);
}

/* Full viewport width, unlike .wrap: the CTA is meant to sit at the far
   right of the screen, not at the 1280px content boundary. The gutter is
   what keeps it off the edge. */
.nav__inner {
  width: 100%;
  padding-inline: var(--gutter);
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nav__logo {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.nav__menu {
  display: flex;
  align-items: center;
  gap: clamp(1.25rem, 2.5vw, 2.25rem);
  font-size: 0.9rem;
}

/* No logo in the bar, so the menu spans it and centres its links. The
   CTA is absolutely positioned rather than an in-flow sibling: in flow
   its width would push the menu's centre leftward by half the pill,
   knocking the links off true centre.
   Desktop only, since the mobile menu is a stacked panel (see @media). */
@media (min-width: 861px) {
  .nav__inner { position: relative; }

  /* Centred with inset + margin rather than translateY(-50%), so the
     transform stays free for .btn:hover's lift. Sharing the property
     meant the hover rule dropped the centring and the pill jumped. */
  .nav__cta {
    position: absolute;
    right: var(--gutter);
    top: 0;
    bottom: 0;
    margin-block: auto;
    height: fit-content;
  }

  .nav__menu {
    flex: 1;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 0;
  }
  /* .nav__links holds the centre track, so adding or removing a link
     needs no rule changes here. Track 3 is the spacer that balances the
     pill sitting outside the menu, keeping the links optically centred. */
  .nav__links {
    grid-column: 2;
    display: flex;
    align-items: center;
    gap: clamp(1.25rem, 2.5vw, 2.25rem);
  }
}

.nav__menu a:not(.btn) {
  color: var(--ink-soft);
  transition: color 0.2s ease;
}
.nav__menu a:not(.btn):hover { color: var(--ink); }

.nav__checkbox, .nav__burger { display: none; }

/* ── Nav over a dark band ─────────────────────────────────
   The bar is translucent white, so a black section scrolling beneath it
   shows as a bright strip. script.js adds .nav--over-dark while any
   .section--dark sits behind the bar; everything here just re-tints. */
.nav--over-dark {
  background: color-mix(in srgb, #000 55%, transparent);
  border-bottom-color: rgba(255, 255, 255, 0.14);
}
.nav--over-dark .nav__menu a:not(.btn) { color: rgba(255, 255, 255, 0.72); }
.nav--over-dark .nav__menu a:not(.btn):hover { color: #fff; }
.nav--over-dark .brand-mark { filter: invert(1); }
.nav--over-dark .nav__burger span { background: #fff; }

/* The pill is black-on-white by default; flip it so it stays legible. */
.nav--over-dark .btn {
  background: #fff;
  color: #111;
  border-color: #fff;
}

/* Colour only. Never transition layout properties on a sticky bar. */
.nav,
.nav .brand-mark,
.nav__menu a,
.nav .btn {
  transition: background 0.25s ease, color 0.25s ease,
              border-color 0.25s ease, filter 0.25s ease;
}

@media (prefers-reduced-motion: reduce) {
  .nav, .nav .brand-mark, .nav__menu a, .nav .btn { transition: none; }
}

/* ── Hero ─────────────────────────────────────────────── */

/* Logo tokens. Tune the overlay without touching the rules below. */
:root {
  /* logo.png is cropped to the wordmark, so this width is the mark's
     actual size. (It previously carried ~35% transparent margin per
     side, which is why the old value was far larger.) */
  --hero-logo-w:     clamp(120px, 13vw, 200px);  /* signature size    */
  --hero-scrim:      0.28;                        /* 0 = no dim       */
  --hero-logo-invert: 0;                          /* 1 = invert logo  */
  --hero-bar-h:      8px;                         /* bar under hero   */
}

.hero {
  position: relative;
  isolation: isolate;
}

.hero__img {
  width: 100%;
  height: clamp(340px, 68vh, 760px);
  object-fit: cover;
}

/* Placement layer + scrim. The mark sits bottom-right like a signature
   on a print, clear of the car. Change place-items to reposition it. */
.hero__overlay {
  position: absolute;
  inset: 0;
  z-index: 3;                /* above .photo grain and vignette */
  display: grid;
  place-items: end end;
  padding: clamp(1.25rem, 3.5vw, 3rem);
  background: radial-gradient(
    ellipse at center,
    rgba(255, 255, 255, var(--hero-scrim)) 0%,
    rgba(255, 255, 255, 0) 62%
  );
  pointer-events: none;
}

.hero__logo {
  width: var(--hero-logo-w);
  height: auto;
  filter: invert(var(--hero-logo-invert))
          drop-shadow(0 2px 18px rgba(0, 0, 0, 0.18));
  display: block;
}

/* ── Logo link ────────────────────────────────────────────
   The signature doubles as the LinkedIn link. */
.hero__logo-wrap {
  position: relative;
  display: inline-block;
  /* .hero__overlay sets pointer-events: none so the scrim never blocks
     the page; the link has to opt back in to be clickable. */
  pointer-events: auto;
  transition: transform 0.3s ease, filter 0.3s ease;
}

.hero__logo-wrap:hover { transform: translateY(-2px); }
.hero__logo-wrap:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 12px;
  border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .hero__logo-wrap { transition: none; }
  .hero__logo-wrap:hover { transform: none; }
}


/* Dark scrim, for a light hero photo carrying a white logo, or a
   naturally dark hero. Add `hero--dark` to the <section class="hero">.
   Set --hero-logo-invert: 1 alongside it only if your logo is black. */
/* Follows the mark to the bottom-right corner. Softer and tighter than
   the old centre-left version: a signature needs just enough separation
   from the plate, not a dimmed half-frame. */
.hero--dark .hero__overlay {
  background: radial-gradient(
    ellipse at 82% 88%,
    rgba(0, 0, 0, 0.45) 0%,
    rgba(0, 0, 0, 0.12) 40%,
    rgba(0, 0, 0, 0) 65%
  );
}

/* Thin black rule under the hero, edge to edge. */
.hero-bar {
  height: var(--hero-bar-h);
  background: #000;
}

/* ── Type ─────────────────────────────────────────────── */

.eyebrow {
  margin: 0 0 1.25rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.title { font-size: clamp(2.75rem, 8vw, 5.5rem); }

.lead {
  margin: 1.75rem 0 0;
  font-size: clamp(1.15rem, 2vw, 1.4rem);
  line-height: 1.5;
  color: var(--ink-soft);
}

/* Short centered rule, a quiet break between sections.
   Width/weight are tuned to read as punctuation, not structure. */
.rule-short {
  width: 56px;
  height: 0;
  margin: 0 auto;
  border: 0;
  border-top: 1px solid var(--ink);
}

.prose > * + *  { margin-top: 1.5rem; }
.prose p        { margin: 0; }
.prose h2       { font-size: clamp(1.5rem, 3vw, 2rem); margin-top: 3rem; }
.prose h2:first-child { margin-top: 0; }
/* Overrides the generic `* + *` gap above, giving a heading more air
   beneath it than an ordinary paragraph break. */
.prose h2 + *   { margin-top: 2rem; }

/* Opt-in centring for a single heading; the body copy stays left. */
.h-center { text-align: center; }

/* ── Figures ──────────────────────────────────────────── */

.figure { margin: 0; }

.figure img { width: 100%; border-radius: var(--radius); }

.figure--full img { border-radius: 0; }

.figure figcaption {
  margin-top: 0.85rem;
  padding-inline: var(--gutter);
  font-size: 0.82rem;
  color: var(--ink-soft);
}
.figure:not(.figure--full) figcaption { padding-inline: 0; }

/* ── Photographic overlay ─────────────────────────────────
   Add `photo` to a .figure or .hero to break up the clean-CG
   look: grain, vignette, and a faint edge warmth. Cosmetic only, since
   real depth of field / bloom / lens dirt belong in-engine.
   Remove the class once renders have proper post-processing. */

:root {
  --grain:    0.055;   /* 0.03 subtle · 0.09 heavy   */
  --vignette: 0.28;    /* corner darkening           */
}

.photo { position: relative; isolation: isolate; }

.photo > img,
.photo > video {
  /* A touch off perfect: real sensors aren't this neutral. */
  filter: contrast(1.02) saturate(0.98);
}

/* Vignette + a warm/cool edge cast, the way a real lens falls off. */
.photo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(ellipse at 50% 45%,
      rgba(0,0,0,0) 55%,
      rgba(0,0,0,var(--vignette)) 100%),
    linear-gradient(100deg,
      rgba(120,160,255,0.05) 0%,
      rgba(0,0,0,0) 35%,
      rgba(0,0,0,0) 65%,
      rgba(255,180,120,0.05) 100%);
}

/* Fixed-size SVG noise tile. Stays the same grain size regardless
   of how large the image scales, like real film. */
.photo::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: var(--grain);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* Captions sit outside the treated area. */
.photo figcaption { position: relative; z-index: 3; }

/* Heavier variant. */
.photo--strong { --grain: 0.09; --vignette: 0.42; }

.grid { display: grid; gap: clamp(1rem, 2.5vw, 2rem); }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }

/* A titled chapter: centered intro copy over a full-width media block.
   The extra top space separates it from whatever preceded it, so two
   video sections in a row don't read as a repeating pair. */
.chapter {
  padding-top: calc(var(--section) * 1.5);
  border-top: 1px solid var(--rule);
}

.chapter__intro {
  text-align: center;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}
.chapter__intro h2 {
  font-size: clamp(1.75rem, 3.5vw, 2.6rem);
  margin-bottom: 1.1rem;
}
.chapter__intro p { color: var(--ink-soft); }

/* ── Pulsing border ───────────────────────────────────────
   The whole frame breathes together: a flat border plus a blurred copy
   behind it for the glow. No conic gradient or angle animation needed,
   since nothing travels around the edge any more. */

.chaser {
  --pulse-w:    2px;    /* line thickness              */
  --pulse-time: 4s;     /* one breath, in and back out */
  --pulse-low:  0.18;   /* dimmest point               */
  --pulse-high: 0.85;   /* brightest point             */

  position: relative;
  border-radius: var(--radius);
}

.chaser::before,
.chaser::after {
  content: "";
  position: absolute;
  /* Sit just outside the video so the line frames it, not overlaps it. */
  inset: calc(var(--pulse-w) * -1);
  border-radius: inherit;
  pointer-events: none;
  border: var(--pulse-w) solid #fff;
  animation: border-pulse var(--pulse-time) ease-in-out infinite;
}

/* Blurred copy behind, so the pulse carries a soft halo rather than
   just changing the line's opacity. */
.chaser::after {
  filter: blur(7px);
  z-index: -1;
}

/* Breathe: dim, up to full, back down. Animating opacity only keeps
   this on the compositor, so it costs nothing during video playback. */
@keyframes border-pulse {
  0%, 100% { opacity: var(--pulse-low); }
  50%      { opacity: var(--pulse-high); }
}

/* Runs only while idle. script.js sets .is-idle from the media events.
   Fading out rather than stopping mid-breath avoids a frozen frame. */
.chaser::before,
.chaser::after {
  animation-play-state: paused;
  opacity: 0;
  transition: opacity 0.45s ease;
}
.chaser.is-idle::before,
.chaser.is-idle::after { animation-play-state: running; }

/* No JS: .is-idle never lands, so fall back to always-on. */
.no-js .chaser::before,
.no-js .chaser::after { animation-play-state: running; }

/* A slow fade is far gentler than a moving ring, but honour the
   preference anyway and leave a steady hairline. */
@media (prefers-reduced-motion: reduce) {
  .chaser::before {
    animation: none;
    opacity: 0.22;
    transition: none;
  }
  .chaser::after { display: none; }
}

.video {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--bg-alt);
  border-radius: var(--radius);
  overflow: hidden;
}
.video iframe,
.video video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}

/* ── Architecture diagram ─────────────────────────────── */

.diagram {
  background: var(--bg-alt);
  /* Matches the weight of the full-bleed bar under the hero. */
  border: var(--hero-bar-h) solid #000;
  border-radius: var(--radius);
  padding: clamp(1rem, 3vw, 2.5rem);
  overflow-x: auto;          /* narrow screens scroll rather than squash */
}
.diagram svg {
  width: 100%;
  min-width: 620px;          /* below this the labels stop being legible */
  height: auto;
  display: block;
}

.dg-node rect {
  fill: var(--bg);
  stroke: var(--rule);
  stroke-width: 1.5;
}
.dg-node--accent rect { stroke: var(--ink); stroke-width: 2; }
.dg-node--sub rect    { fill: none; stroke-dasharray: 4 4; }

.dg-t, .dg-s {
  text-anchor: middle;
  font-family: var(--font);
  fill: var(--ink);
}
.dg-t { font-size: 15px; font-weight: 600; }
.dg-s { font-size: 12px; fill: var(--ink-soft); }
.dg-head { font-weight: 600; fill: var(--ink); }
.dg-port {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
}

.dg-edge {
  fill: none;
  stroke: var(--ink-soft);
  stroke-width: 1.5;
}
.dg-edge--dash  { stroke-dasharray: 5 4; }
.dg-arrow-head  { fill: var(--ink-soft); }

.dg-l {
  text-anchor: middle;
  font-family: var(--font);
  font-size: 11px;
  fill: var(--ink-soft);
  letter-spacing: 0.06em;
}
.dg-l--left { text-anchor: start; }

/* ── Brand mark ───────────────────────────────────────────
   The logo art is white, made for dark backgrounds. On the light
   shell it needs inverting for the nav and footer; over the hero
   it sits on a dark scrim and stays as-is.
   Replace with a dark-on-transparent export when there is one. */

.brand-mark {
  height: 26px;
  width: auto;
  filter: invert(1) hue-rotate(180deg);
}
.footer .brand-mark { height: 24px; }

/* ── Credits ──────────────────────────────────────────── */

.credits {
  margin: 0;
  border-top: 1px solid var(--rule);
}
.credits > div {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 1rem;
  padding: 1.1rem 0;
  border-bottom: 1px solid var(--rule);
}
.credits dt {
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding-top: 0.2rem;
}
.credits dd { margin: 0; }

/* Nothing else in the credits list is clickable, so an unstyled link
   would be invisible. Underline marks it without adding a colour. */
.credits__link {
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--rule);
  transition: text-decoration-color 0.2s ease;
}
.credits__link:hover { text-decoration-color: var(--ink); }

.disclaimer {
  margin: 1.75rem 0 0;
  font-size: 0.78rem;
  line-height: 1.6;
  color: var(--ink-soft);
  opacity: 0.75;
}

/* ── Buttons + CTA ────────────────────────────────────── */

.btn {
  display: inline-block;
  padding: 0.9rem 2rem;
  background: var(--accent);
  color: var(--bg);
  border-radius: 999px;
  font-size: 0.9rem;
  font-weight: 600;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.btn:hover { opacity: 0.82; transform: translateY(-1px); }
.btn--sm { padding: 0.6rem 1.35rem; font-size: 0.82rem; }

/* Contact pills swap their label to a confirmation on click. Reserving
   the width stops the nav reflowing as the text changes length. */
.js-copy-mail {
  min-width: 11ch;
  text-align: center;
  white-space: nowrap;
}
.js-copy-mail.is-copied {
  background: #1f7a3d;      /* success green, distinct from the black pill */
  color: #fff;
}
/* Nav variant sits on the dark bar, where the pill is inverted. */
.nav--over-dark .js-copy-mail.is-copied {
  background: #4ade80;
  color: #06301a;
  border-color: #4ade80;
}

.cta {
  background: var(--bg-alt);
  padding-block: var(--section);
  text-align: center;
}
.cta h2 { font-size: clamp(1.85rem, 4vw, 3rem); }
.cta p  { margin: 1rem 0 2.25rem; color: var(--ink-soft); }

/* ── Footer ───────────────────────────────────────────── */

.footer {
  border-top: 1px solid var(--rule);
  padding-block: 3.5rem;
}
/* No logo here any more, so the links centre instead of sitting
   opposite it. */
.footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
}
.footer__links {
  display: flex;
  flex-wrap: wrap;          /* long email wraps rather than overflowing */
  justify-content: center;
  gap: 1.75rem;
  font-size: 0.88rem;
  color: var(--ink-soft);
}
.footer__links a { font-weight: 600; }
.footer__links a:hover { color: var(--ink); }
.footer__copy {
  margin: 0;
  width: 100%;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
  font-size: 0.8rem;
  color: var(--ink-soft);
  text-align: center;       /* match the links above it */
}

/* ── Reveal on scroll ─────────────────────────────────── */

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}
.reveal.is-visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
}

/* ── Mobile ───────────────────────────────────────────── */

@media (max-width: 860px) {
  .grid--2, .grid--3 { grid-template-columns: 1fr; }

  .chapter { padding-top: calc(var(--section) * 1.1); }
  .credits > div     { grid-template-columns: 1fr; gap: 0.25rem; }

  /* The signature keeps its corner on phones, just smaller and tucked
     closer in. */
  .hero {
    --hero-logo-w: clamp(100px, 28vw, 150px);
  }
  .hero__overlay { padding: 1rem 1.1rem; }

  /* Burger left, CTA right: the auto margin between them absorbs all the
     free space in the bar. */
  .nav__burger {
    margin-right: auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 28px;
    height: 28px;
    cursor: pointer;
  }
  .nav__burger span {
    display: block;
    height: 2px;
    background: var(--ink);
    transition: transform 0.25s ease, opacity 0.25s ease;
  }

  /* The desktop centre-track wrapper is an inline span; in the stacked
     panel it has to become a column of its own. */
  .nav__links {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1.5rem;
  }

  .nav__menu {
    position: fixed;
    inset: var(--nav-h) 0 auto 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 1.5rem;
    padding: 2rem var(--gutter) 2.5rem;
    background: var(--bg);
    border-bottom: 1px solid var(--rule);
    font-size: 1.15rem;
    transform: translateY(-8px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease;
  }

  /* The CTA sits in the bar, not the drop-down panel, so it stays
     reachable whether the menu is open or closed. */
  .nav__cta { flex-shrink: 0; }
  .nav__checkbox:checked ~ .nav__menu {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }
  .nav__checkbox:checked ~ .nav__burger span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav__checkbox:checked ~ .nav__burger span:nth-child(2) { opacity: 0; }
  .nav__checkbox:checked ~ .nav__burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
}
