/* ─────────────────────────────────────────────────────────────────────
   PP Fragment — five static OTF cuts, one logical face per file.

   We previously served a single variable woff2 with `font-variation-settings`
   descriptors inside each @font-face to pin Sans vs Glare and
   weight. WebKit (Safari 26.4 confirmed) does not apply the
   font-variation-settings descriptor inside @font-face, so every
   face fell back to the variable file's default instance and
   "PP Fragment Glare" rendered as Sans. Static cuts are now the
   source of truth for the public site.

   The variable woff2 is still loaded under "PP Fragment VF" further
   down for the font-morph animation; the standalone CV
   (_source/cv/cv.html) loads it directly from disk.
   ───────────────────────────────────────────────────────────────────── */

@font-face {
  font-family: "PP Fragment Glare";
  src: url("fonts/PPFragment-GlareRegular.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "PP Fragment Glare";
  src: url("fonts/PPFragment-GlareExtraBold.otf") format("opentype");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "PP Fragment Sans";
  src: url("fonts/PPFragment-SansLight.otf") format("opentype");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "PP Fragment Sans";
  src: url("fonts/PPFragment-SansRegular.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "PP Fragment Sans";
  src: url("fonts/PPFragment-SansBold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "PP Fragment Sans";
  src: url("fonts/PPFragment-SansExtraBold.otf") format("opentype");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

/* Variable file kept under a distinct family name so it can't shadow
   the static cuts above. Used only by the font-morph animation
   wordmark (.font-morph__word) which sets font-variation-settings
   per frame from JS. */
@font-face {
  font-family: "PP Fragment VF";
  src: url("fonts/PPFragment-Variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ─────────────────────────────────────────────────────────────────────
   Tokens — mirroring the artboard so swapping a value in one place
   propagates everywhere.
   ───────────────────────────────────────────────────────────────────── */

:root {
  /* surface */
  --bg: #0d0d0d;
  --fg: #ffffff;
  --fg-dim: rgba(255, 255, 255, 0.65);
  --fg-faint: rgba(255, 255, 255, 0.45);

  --pill-bg: #ffffff;
  --pill-fg: #0d0d0d;
  --pill-fg-dim: rgba(13, 13, 13, 0.65);

  --card-bg: #000000; /* card on the white pill */
  --card-bg-dark: rgba(
    0,
    0,
    0,
    0.35
  ); /* frosted glass on the dark page bg (testimonials) */
  --card-border: rgba(255, 255, 255, 0.15);

  --accent: #fd5a68;
  --glow: rgba(
    253,
    90,
    104,
    0.1
  ); /* --accent at 10% opacity — atmospheric, lifts under spotlights */

  /* shape */
  --radius-pill: clamp(28px, 4vw, 50px);
  --radius-card: 24px;
  --radius-media: 10px;
  --shadow-card: 0 3px 7px rgba(0, 0, 0, 0.3);
  --shadow-button: 0 2px 4px rgba(0, 0, 0, 0.1);

  /* type */
  --font-display: "PP Fragment Glare", "Times New Roman", serif;
  --font-text:
    "PP Fragment Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, monospace;

  --fs-display: clamp(40px, 5.5vw, 56px);
  --fs-h2: clamp(26px, 3vw, 36px);
  --fs-h3: clamp(20px, 2vw, 24px);
  --fs-body: clamp(17px, 1.4vw, 20px);
  --fs-small: 14px;

  --lh-tight: 1.15;
  --lh-snug: 1.35;
  --lh-body: 1.6;

  /* layout */
  --container: 1280px;
  --gutter: clamp(16px, 4vw, 64px);
  --section-y: clamp(40px, 6vw, 88px);
}

/* ─────────────────────────────────────────────────────────────────────
   Reset (just enough)
   ───────────────────────────────────────────────────────────────────── */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-text);
  font-weight: 400;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Safety net: if anything ever introduces inline overflow at narrow
     widths, clip at the viewport rather than letting the page scroll
     sideways. `clip` (vs `hidden`) does not create a scroll container,
     so position: sticky and the case-modal FLIP stay unaffected. */
  overflow-x: clip;
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: var(--lh-tight);
  margin: 0;
}

p {
  margin: 0;
}

ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

a {
  color: inherit;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover,
a:focus-visible {
  text-decoration-thickness: 2px;
}

a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

img,
svg {
  display: block;
  max-width: 100%;
}

hr {
  border: 0;
}

cite {
  font-style: normal;
}

code {
  font-family: var(--font-mono);
  font-size: clamp(14px, 1.2vw, 18px);
  font-weight: 400;
  color: var(--fg);
  background: rgba(0, 0, 0, 0.1);
  padding: 4px 8px;
  border-radius: 6px;
}

/* skip link for keyboard users */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--fg);
  color: var(--bg);
  padding: 8px 12px;
  z-index: 10;
}

.skip:focus {
  left: 12px;
  top: 12px;
}

/* ─────────────────────────────────────────────────────────────────────
   Container
   ───────────────────────────────────────────────────────────────────── */

.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ─────────────────────────────────────────────────────────────────────
   Utility (top + bottom contact bars)
   ───────────────────────────────────────────────────────────────────── */

.utility {
  padding: var(--gutter);
}

.utility--bottom {
  padding-block: 28px 36px;
}

.utility__nav ul {
  display: flex;
  justify-content: center;
  gap: clamp(16px, 3vw, 28px);
  font-size: var(--fs-small);
  color: var(--fg-dim);
}

.utility__nav a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-decoration: none;
  letter-spacing: 0.02em;
  color: var(--fg-dim);
}

.utility__nav a:hover,
.utility__nav a:focus-visible {
  color: var(--fg);
}

.util-icon {
  width: 1.1em;
  height: 1.1em;
  display: inline-block;
  opacity: 0.75;
  transition: opacity 200ms ease;
}

.utility__nav a:hover .util-icon,
.utility__nav a:focus-visible .util-icon {
  opacity: 1;
}

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

.hero {
  padding-block: clamp(48px, 8vw, 120px) clamp(40px, 6vw, 80px);
  text-align: center;
}

.hero__name {
  font-size: var(--fs-display);
  letter-spacing: -0.01em;
}

.hero__role {
  margin-top: 10px;
  font-weight: 300;
  font-size: clamp(16px, 1.4vw, 20px);
  color: var(--fg-dim);
}

.hero__bio {
  margin: clamp(28px, 4vw, 48px) auto 0;
  max-width: 580px;
  font-weight: 300;
  color: var(--fg);
}

.hero__now {
  margin: 24px auto 0;
  max-width: 580px;
  font-weight: 300;
  color: var(--fg-dim);
}

.hero__now a {
  color: var(--fg);
  text-decoration-color: var(--accent);
}

.brand-link {
  text-decoration-color: var(--accent);
}

.brand-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: 2px;
}

.brand-logo {
  display: inline-block;
  width: auto;
  vertical-align: -0.18em;
}

.brand-logo--evidenced {
  height: 1.1em;
}

/* ─────────────────────────────────────────────────────────────────────
   Timeline — career history rail between hero and value pill.
   Full-bleed rail with five points; items live in a centred container
   so the rightmost (current) entry has breathing room from the edge.
   ───────────────────────────────────────────────────────────────────── */

.timeline {
  padding-top: clamp(24px, 3vw, 48px);
}

.tl {
  position: relative;
}

.tl-rail {
  position: relative;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--card-border) 8%,
    var(--card-border) 92%,
    transparent 100%
  );
}

.tl-items {
  position: relative;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
  height: 190px;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}

.tl-item {
  position: relative;
  flex: 0 0 auto;
  /* Scales with viewport so adjacent items can never collide at narrower
     widths. At ~870px viewport this resolves to ~135px, leaving a safe
     gap between neighbouring items. */
  width: clamp(120px, 15.5vw, 180px);
}

.tl-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  transform: translate(-50%, -50%);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #626262;
  transition:
    transform 300ms ease,
    background-color 300ms ease,
    box-shadow 300ms ease;
  z-index: 2;
}

.tl-item.is-current::before {
  width: 10px;
  height: 10px;
  background: var(--accent);
  box-shadow: 0 0 0 5px rgba(253, 90, 104, 0.15);
}

.tl-item:hover::before {
  transform: translate(-50%, -50%) scale(1.3);
  background: var(--accent);
}

.tl-content {
  padding-top: 24px;
}

.tl-year {
  font-family: var(--font-text);
  font-weight: 300;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 8px;
}

.tl-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(253, 90, 104, 0.15);
  color: var(--accent);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.08em;
  vertical-align: 1px;
}

.tl-role {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(17px, 1.5vw, 19px);
  line-height: 1.25;
  color: var(--fg);
  margin-bottom: 12px;
}

.tl-logo {
  display: block;
  color: var(--fg);
  line-height: 0;
  opacity: 0.7;
  transition: opacity 300ms ease;
}

.tl-item:hover .tl-logo,
.tl-item.is-current .tl-logo {
  opacity: 1;
}

.tl-logo svg {
  display: block;
  width: auto;
  max-width: 100%;
}

/* Per-logo optical sizing — the wordmarks have very different cap heights,
   so each is tuned to read at roughly the same visual weight. */
.tl-logo--preloaded svg {
  height: 22px;
}
.tl-logo--ostmodern svg {
  height: 26px;
}
.tl-logo--rga svg {
  height: 14px;
}
.tl-logo--trail svg {
  height: 20px;
  margin-left: -1px;
}
.tl-logo--evidenced svg {
  height: 21px;
  margin-left: -2px;
}

/* Timeline hovercard — appears above the dot (or below if there's no room).
   Card's left edge anchors on the dot; horizontal nudges from the JS arrive
   as --tl-card-shift so the rightmost item can slide left to stay on-screen. */
.tl-card {
  position: absolute;
  bottom: calc(100% + 18px);
  left: 0;
  width: clamp(200px, 22vw, 240px);
  padding: 14px 16px;
  background: var(--pill-bg);
  color: var(--pill-fg);
  border-radius: 14px;
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.18),
    0 12px 32px rgba(0, 0, 0, 0.32);
  font-family: var(--font-text);
  font-weight: 300;
  font-size: 13.5px;
  line-height: 1.45;
  opacity: 0;
  pointer-events: none;
  transform: translate(var(--tl-card-shift, 0px), 4px);
  transition:
    opacity 160ms ease,
    transform 160ms ease;
  z-index: 5;
}

.tl-item.is-open .tl-card {
  opacity: 1;
  pointer-events: auto;
  transform: translate(var(--tl-card-shift, 0px), 0);
}

.tl-item[data-tl-card-flip="below"] .tl-card {
  bottom: auto;
  top: calc(100% + 18px);
  transform: translate(var(--tl-card-shift, 0px), -4px);
}

.tl-item[data-tl-card-flip="below"].is-open .tl-card {
  transform: translate(var(--tl-card-shift, 0px), 0);
}

.tl-card__text {
  margin: 0;
  color: var(--pill-fg);
}

@media (max-width: 640px) {
  .timeline {
    padding-block: clamp(40px, 8vw, 64px);
  }

  .tl {
    position: relative;
    padding-left: calc(var(--gutter) + 24px);
  }

  .tl-rail {
    position: absolute;
    left: var(--gutter);
    top: 0;
    bottom: 0;
    width: 1px;
    height: 100%;
    background: linear-gradient(
      180deg,
      transparent 0%,
      var(--card-border) 6%,
      var(--card-border) 94%,
      transparent 100%
    );
  }

  .tl-items {
    position: relative;
    max-width: 100%;
    padding: 0 var(--gutter) 0 0;
    height: auto;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
  }

  .tl-item {
    position: relative;
    top: auto !important;
    left: auto !important;
    width: 100%;
    padding: 0 0 32px 0;
  }

  .tl-item::before {
    /* Rail sits at left:var(--gutter) within .tl, items start 24px to the
       right of the rail. -24px places the dot's centre on the rail; the
       extra -4.5px (half the 9px dot) centres it. */
    left: -28.5px;
    top: 8px;
    transform: none;
  }

  .tl-item.is-current::before {
    left: -29px;
    transform: none;
  }

  /* Override the desktop hover transform — it includes a -50% translate
     that would knock the dot off the rail since mobile dots are positioned
     with absolute offsets, not by centring on left: 0. */
  .tl-item:hover::before {
    transform: scale(1.3);
  }

  .tl-content {
    padding-top: 0;
  }

  /* Mobile: show the copy inline beneath each item instead of as a
     hovercard. Reset positioning and stay always-visible. Visual treatment
     matches the testimonial cards (frosted dark surface) for consistency. */
  .tl-card {
    position: static;
    width: 100%;
    margin-top: 12px;
    padding: 14px 16px;
    background: var(--card-bg-dark);
    -webkit-backdrop-filter: blur(14px) saturate(1.2);
    backdrop-filter: blur(14px) saturate(1.2);
    color: var(--fg);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 14px;
    box-shadow: var(--shadow-card);
    opacity: 1;
    pointer-events: auto;
    transform: none;
    transition: none;
  }

  .tl-item.is-open .tl-card,
  .tl-item[data-tl-card-flip="below"] .tl-card,
  .tl-item[data-tl-card-flip="below"].is-open .tl-card {
    transform: none;
  }

  .tl-card__text {
    color: var(--fg);
  }

  /* Mobile order: current first, then past chronologically descending. */
  .tl-item--evidenced {
    order: 1;
  }
  .tl-item--trail {
    order: 2;
  }
  .tl-item--rga {
    order: 3;
  }
  .tl-item--ostmodern {
    order: 4;
  }
  .tl-item--preloaded {
    order: 5;
  }
}

/* ─────────────────────────────────────────────────────────────────────
   CV download — small bordered pill that sits between the timeline
   and the value pill. Inherits dark-surface text colours; the icon
   uses currentColor so it tracks the label on hover/focus.
   ───────────────────────────────────────────────────────────────────── */

.cv-cta-wrap {
  display: flex;
  justify-content: center;
}

.cv-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px 10px 14px;
  border: 1px solid var(--card-border);
  border-radius: 999px;
  color: var(--fg-dim);
  font-size: var(--fs-small);
  letter-spacing: 0.02em;
  text-decoration: none;
  background: var(--card-bg);
  box-shadow: var(--shadow-card);
  transition:
    color 150ms ease,
    border-color 150ms ease,
    background 150ms ease;
}

.cv-cta:hover,
.cv-cta:focus-visible {
  color: var(--fg);
  border-color: rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.03);
}

.cv-cta:focus-visible {
  outline: 2px solid transparent;
  border-radius: 999px;
  box-shadow:
    var(--shadow-card),
    0 0 0 3px var(--accent);
}

.cv-cta__icon {
  width: 1.6em;
  height: 1.6em;
  flex: none;
}

/* ─────────────────────────────────────────────────────────────────────
   Value section — white "pill" container with the card grid
   ───────────────────────────────────────────────────────────────────── */

.value {
  padding-block: var(--section-y) calc(var(--section-y) * 0.6);
}

.value__pill {
  background: var(--pill-bg);
  color: var(--pill-fg);
  border-radius: var(--radius-pill);
  padding: clamp(32px, 6vw, 80px) clamp(16px, 4vw, 60px);
}

/* Lock <html> — the document scroller — for the open lifetime of the
   modal. Body-level `overflow: hidden` does NOT pin scroll on this
   layout because the scrollbar lives on <html>, which is why the close
   morph used to "jump about". `overscroll-behavior: none` stops the
   viewport rubber-band so the page can't peek through behind the
   fixed modal layer at top/bottom. */
html.is-modal-locked {
  overflow: hidden;
  overscroll-behavior: none;
}

.value__heading {
  text-align: center;
  font-size: var(--fs-h2);
  margin-bottom: 0;
}

.value__heading + .cards,
.value__footnote + .cards {
  margin-top: clamp(28px, 4vw, 48px);
}

.cards + .value__heading {
  margin-top: clamp(56px, 7vw, 96px);
}

/* Value cards stay as a 2x2 grid on anything tablet-width and up.
   Below that they collapse to a single column. We don't use auto-fit here
   because at wide widths it would happily flow into 3 or 4 columns and
   break the intentional 2x2 composition from the artboard. */
.cards {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(16px, 2vw, 28px);
}

@media (min-width: 640px) {
  .cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: 1fr;
  }
}

.card {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: clamp(24px, 3vw, 36px);
  height: 100%;
  /* Prevent intrinsic min-content from flex children (inline logos,
     tool-icons row) from re-inflating the card beyond its grid track. */
  min-width: 0;
  box-shadow: var(--shadow-card);
  background: var(--card-bg);
  color: var(--fg);
  border-radius: var(--radius-card);
  border: 1px solid var(--card-border);

  text-decoration: none;
  transition:
    background 200ms ease,
    border-color 200ms ease;
}

.card:hover {
  background: rgba(0, 0, 0, 0.92);
  border-color: rgba(255, 255, 255, 0.3);
}

.card:focus-visible {
  outline: 2px solid transparent;
  border-radius: var(--radius-card);
  box-shadow:
    var(--shadow-card),
    0 0 0 3px var(--accent);
}

.card__content {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 10px;
}

.card__title {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  line-height: var(--lh-snug);
  font-weight: 400;
  color: var(--fg);
}

.card__desc {
  margin: 0;
  font-size: clamp(16px, 1.3vw, 18px);
  line-height: 1.5;
  font-weight: 300;
  color: var(--fg);
}

.card__cta {
  display: inline-flex;
  width: fit-content;
  align-items: center;
  gap: 5px;
  margin-top: auto;
  color: var(--fg-dim);
  font-size: var(--fs-small);
  line-height: 1.2;
  letter-spacing: 0.02em;
  transition: color 180ms ease;
}

.card__cta svg {
  width: 1.5em;
  height: 1.5em;
  flex: none;
  transition: transform 180ms ease;
}

.card:hover .card__cta,
.card:focus-visible .card__cta {
  color: var(--fg);
}

.card:hover .card__cta svg,
.card:focus-visible .card__cta svg {
  transform: translateX(2px);
}

/* Bold runs inside body copy use the ExtraBold cut of PP Fragment Sans —
   matches the artboard's emphasised phrases. */
.card__desc strong,
.hero__bio strong {
  font-family: var(--font-text);
  font-weight: 600;
  color: var(--fg);
}

/* Card 2 — three inline colour swatches representing the design-system
   palette. Sized to the surrounding text and nudged down slightly so they
   sit on the cap-height line. */
.swatches {
  display: inline-flex;
  gap: 5px;
  vertical-align: -0.18em;
}

.swatch {
  display: inline-block;
  width: 0.95em;
  height: 0.95em;
  border-radius: 5px;
}

.swatch--red {
  background: #fd5a68;
}
.swatch--orange {
  background: #f2b16b;
}
.swatch--green {
  background: #34b780;
}
.swatch--accent {
  background: var(--accent);
}
/* Card 4 — pre-tiled tool-icon SVGs (Cursor, OpenAI, Claude) plus the
   Pencil PNG. These already ship with their own background colour, so we
   only size + corner-round consistently. */
.tool-icons {
  display: inline-flex;
  gap: 4px;
  vertical-align: -0.32em;
}

.tool-icon {
  width: 1.25em;
  height: 1.25em;
  display: inline-block;
}

.tool-icon--posthog {
  width: 38px;
  height: 25px;
}

/* Inline brand logos that flow inside body copy (e.on in Card 1, Figma
   in Card 3). Sized to the surrounding text and nudged down so the
   wordmark baseline sits on the text baseline. */
.inline-logo {
  display: inline-block;
  width: auto;
  vertical-align: -0.22em;
}

.inline-logo--eon {
  height: 1em;
}
.inline-logo--figma {
  height: 1.1em;
}

.value__footnote {
  margin-top: clamp(8px, 1vw, 14px);
  text-align: center;
  font-weight: 300;
  font-size: 16px;
  color: var(--pill-fg-dim);
}

/* ─────────────────────────────────────────────────────────────────────
   References — testimonials on dark
   ───────────────────────────────────────────────────────────────────── */

.references {
  padding-block: var(--section-y);
}

.references__heading {
  text-align: center;
  font-size: var(--fs-h2);
  margin-bottom: clamp(28px, 4vw, 48px);
}

.testimonials {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: clamp(16px, 2vw, 28px);
}

.testimonial {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: clamp(28px, 3vw, 40px);

  background: var(--card-bg-dark);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  border-radius: var(--radius-card);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: var(--shadow-card);
}

.testimonial__quote {
  margin: 0;
  font-weight: 300;
  font-size: 17px;
  line-height: 1.55;
  color: var(--fg);
}

.testimonial__rule {
  width: 48px;
  height: 1px;
  margin: 0;
  background: rgba(255, 255, 255, 0.2);
}

.testimonial__cite {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 14px;
}

.testimonial__avatar {
  display: block;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
}

.testimonial__cite-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.testimonial__name {
  font-weight: 400;
  font-size: 16px;
  color: var(--fg);
}

.testimonial__role {
  font-weight: 300;
  font-size: 14px;
  color: var(--fg-dim);
}

.testimonial__role a {
  text-decoration-color: var(--accent);
}

/* ─────────────────────────────────────────────────────────────────────
   Card visuals — animated SVG/DOM widgets that sit above .card__desc.
   Built by /scripts/visuals/* and mounted into .card__visual.
   Visuals are decorative; copy carries the meaning (aria-hidden in HTML).
   ───────────────────────────────────────────────────────────────────── */

.card__visual {
  flex: 0 0 auto;
  width: 100%;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;
  box-sizing: border-box;
  aspect-ratio: 4 / 3;

  @media (min-width: 450px) {
    aspect-ratio: 4 / 2;
  }
}

.card__visual svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

/* GrowthChart ------------------------------------------------------- */

.growth-chart {
  width: 100%;
  position: relative;
}

.growth-chart__legend {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  font-family: var(--font-text);
  color: var(--fg-faint);
  pointer-events: none;
}

.growth-chart__legend-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.growth-chart__legend-swatch {
  width: 12px;
  height: 2px;
  border-radius: 1px;
}

/* TokenList --------------------------------------------------------- */

/* Card 2 — design tokens listed on the left, an arrow, then a green
   "Get started" pill on the right showing the tokens being applied to a
   real component. Mono aesthetic intentionally mirrors the AI-workflow
   card so the two right-column visuals in row 2 read as a pair. */
.card[data-case="design-system"] .card__visual {
  --font-mono: ui-monospace, "SF Mono", Menlo, monospace;
  font-family: var(--font-mono);
  /* Mono scales with viewport so the row content doesn't outgrow the card
     at the narrow end of the 2-column breakpoint. */
  font-size: clamp(11px, 1.2vw, 12.5px);
}

.token-list {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 32px);
  width: 100%;
  height: 100%;
  padding: clamp(10px, 1.6vw, 20px);
  box-sizing: border-box;
}

.token-list__rows {
  display: flex;
  flex-direction: column;
  row-gap: clamp(6px, 1vw, 10px);
  flex: 1;
  min-width: 0;
}

.token-list__row {
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.4vw, 16px);
  will-change: transform, opacity;
}

.token-list__icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.token-list__icon--swatch {
  background: #34b780;
  border-radius: 3px;
}

.token-list__icon--ring {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: 4px;
}

/* Inner 8x8 outlined square, centred inside the 14px box. */
.token-list__icon--dot::before {
  content: "";
  width: 8px;
  height: 8px;
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: 1.5px;
}

.token-list__icon--type {
  font-family: "PP Fragment Glare";
  font-weight: 800;
  color: #34b780;
  font-size: 13px;
  line-height: 1;
}

.token-list__name {
  color: var(--fg-dim);
  white-space: nowrap;
}

.token-list__value {
  margin-left: auto;
  color: var(--fg);
  white-space: nowrap;
}

.token-list__arrow {
  width: clamp(20px, 3vw, 36px);
  flex-shrink: 0;
  color: var(--fg-faint);
  will-change: opacity;
}

.token-list__arrow svg {
  width: 100%;
  height: auto;
  display: block;
}

.token-list__button {
  background: #34b780;
  color: #0d0d0d;
  padding: clamp(8px, 1.3vw, 14px) clamp(14px, 1.8vw, 22px);
  border-radius: 10px;
  font-family: var(--font-text);
  font-weight: 500;
  font-size: clamp(12px, 1.1vw, 16px);
  white-space: nowrap;
  flex-shrink: 0;
  will-change: transform, opacity;
}

/* DesignToLive ------------------------------------------------------ */

.design-to-live {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* PortfolioLayers --------------------------------------------------- */

.portfolio-layers {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* AiPrScreenshots --------------------------------------------------- */

/* Wide modal-figure visual for the AI workflows case study. Sits in
   the same `case-study__figure--wide` slot the dark-hatched
   placeholder used to fill, so layout above and below is unchanged.
   The SVG inside scales to fit via preserveAspectRatio. */
.ai-pr-screenshots {
  width: 100%;
  aspect-ratio: 1040 / 440;
  padding: clamp(16px, 2.4vw, 32px);
  box-sizing: border-box;
  border-radius: var(--radius-card);
  background: #141414;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ai-pr-screenshots__inner {
  width: 100%;
  height: 100%;
}

.ai-pr-screenshots svg {
  display: block;
  width: 100%;
  height: 100%;
}

.sketch-to-cursor {
  width: 100%;
  aspect-ratio: 1040 / 440;
  padding: clamp(16px, 2.4vw, 32px);
  box-sizing: border-box;
  border-radius: var(--radius-card);
  background: #141414;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.sketch-to-cursor__inner {
  width: 100%;
  height: 100%;
}

.sketch-to-cursor svg {
  display: block;
  width: 100%;
  height: 100%;
}

.illustration-skill {
  width: 100%;
  aspect-ratio: 1040 / 440;
  padding: clamp(16px, 2.4vw, 32px);
  box-sizing: border-box;
  border-radius: var(--radius-card);
  background: #141414;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.illustration-skill__inner {
  width: 100%;
  height: 100%;
}

.illustration-skill svg {
  display: block;
  width: 100%;
  height: 100%;
}

.pencil-agents {
  width: 100%;
  aspect-ratio: 1040 / 440;
  padding: clamp(16px, 2.4vw, 32px);
  box-sizing: border-box;
  border-radius: var(--radius-card);
  background: #141414;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.pencil-agents__inner {
  width: 100%;
  height: 100%;
}

.pencil-agents svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* ModalAnim --------------------------------------------------------- */
/* Behind-the-scenes timeline visual for the portfolio-site case study.
   Wraps a 1040x440 SVG stage and a real <button> + <input type="range">
   control bar inside one dark "blueprint" envelope. Stage keeps the
   1040/440 aspect to match the other wide visuals; controls sit below
   so the wrapper isn't aspect-ratio-locked. */
.modal-anim {
  width: 100%;
  padding: clamp(16px, 2.4vw, 32px);
  box-sizing: border-box;
  border-radius: var(--radius-card);
  background: #141414;
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.6vw, 20px);
}

.modal-anim__stage {
  width: 100%;
  aspect-ratio: 1040 / 424;
}

@media (max-width: 640px) {
  .modal-anim__stage {
    aspect-ratio: 624 / 220;
  }
}

.modal-anim__stage svg {
  display: block;
  width: 100%;
  height: 100%;
}

.modal-anim__controls {
  display: flex;
  align-items: center;
  gap: 12px;
}

.modal-anim__play {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: #ffffff;
  border: none;
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
  padding: 0;
  transition: transform 0.15s ease-out;
}

.modal-anim__play:hover {
  transform: scale(1.04);
}

.modal-anim__play:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.modal-anim__play svg {
  width: 14px;
  height: 14px;
  fill: #0d0d0d;
}

/* Pause is the default glyph; play is hidden until paused. Mirror
   handling for both with classes so screen-reader state lives on the
   button (aria-pressed) and visual state lives in CSS. */
.modal-anim__play .modal-anim__glyph-play {
  display: none;
}

.modal-anim__play:not(.is-playing) .modal-anim__glyph-pause {
  display: none;
}

.modal-anim__play:not(.is-playing) .modal-anim__glyph-play {
  display: block;
}

.modal-anim__pill {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 16px;
  height: 48px;
  padding: 0 24px;
  background: #ffffff;
  border-radius: 999px;
  color: #0d0d0d;
  font-size: var(--fs-small);
}

.modal-anim__pill-label {
  flex-shrink: 0;
  font-weight: 500;
}

.modal-anim__scrub {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 1px;
  background: #0d0d0d;
  border-radius: 999px;
  cursor: pointer;
  margin: 0;
  padding: 0;
}

.modal-anim__scrub::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  background: #ffffff;
  border: 1.5px solid #0d0d0d;
  border-radius: 50%;
  cursor: grab;
  margin-top: 0;
}

.modal-anim__scrub::-moz-range-thumb {
  width: 16px;
  height: 16px;
  background: #ffffff;
  border: 1.5px solid #0d0d0d;
  border-radius: 50%;
  cursor: grab;
}

.modal-anim__scrub:active::-webkit-slider-thumb {
  cursor: grabbing;
}

.modal-anim__scrub:active::-moz-range-thumb {
  cursor: grabbing;
}

.modal-anim__scrub:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: 999px;
}

.modal-anim__pill-value {
  flex-shrink: 0;
  font-family:
    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  min-width: 18ch;
  text-align: right;
  color: rgba(13, 13, 13, 0.85);
}

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

/* DotGridSpotlightViz ----------------------------------------------- */

/* Wide figure that mirrors the live .dgs atmospheric layer in
   miniature. Same dark surround as .modal-anim so the two engineering
   visuals on the portfolio-site case sit in the same visual family. */
.dot-grid-viz {
  width: 100%;
  border-radius: var(--radius-card);
  background: #141414;
  overflow: hidden;
}

.dot-grid-viz__stage {
  width: 100%;
  aspect-ratio: 1040 / 360;
}

.dot-grid-viz__stage svg {
  display: block;
  width: 100%;
  height: 100%;
}

.dot-grid-viz__trail {
  stroke: var(--accent);
  fill: none;
}

.dot-grid-viz__marker {
  fill: var(--accent);
}

/* TrailJourney ------------------------------------------------------ */

.trail-journey {
  width: 100%;
  height: 100%;
  position: relative;
}

.trail-journey__label {
  font-family: var(--font-text);
  font-size: 12px;
  fill: var(--fg);
}

.trail-journey__label--faint {
  fill: var(--fg-faint);
}

.trail-journey__label--accent {
  fill: var(--accent);
}

.trail-journey__label--green {
  fill: #34b780;
}

.trail-journey__year {
  font-family: var(--font-text);
  font-size: 11px;
  fill: var(--fg-faint);
}

.trail-journey__axis {
  stroke: rgba(255, 255, 255, 0.18);
  stroke-width: 1;
}

/* AiWorkflow -------------------------------------------------------- */

.card[data-case="ai-workflows"] .card__visual {
  --font-mono: ui-monospace, "SF Mono", Menlo, monospace;
  font-family: var(--font-mono);
  font-size: 12.5px;
}

.ai-workflow {
  width: 100%;
  display: grid;
  grid-template-rows: auto auto 1fr;
  gap: 10px;
  box-sizing: border-box;
}

.ai-workflow__chips {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
}

.ai-workflow__chip {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  color: var(--fg-faint);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
}

.ai-workflow__prompt {
  border-left: 2px solid rgba(253, 90, 104, 0.35);
  padding-left: 0.875rem;
  color: var(--fg-dim);
  min-height: 1.4em;
  white-space: pre-wrap;
  font-family: var(--font-mono);
}

.ai-workflow__response {
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.9);
  min-height: 5.4em;
  font-family: var(--font-mono);
}

.ai-workflow__caret {
  display: inline-block;
  width: 6px;
  height: 13px;
  vertical-align: -2px;
  background: #fd5a68;
  margin-left: 2px;
  animation: ai-workflow-caret 1s step-end infinite;
}

@keyframes ai-workflow-caret {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}

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

@media (max-width: 480px) {
  .hero {
    text-align: left;
  }

  .hero__bio,
  .hero__now {
    margin-inline: 0;
  }

  .utility__nav ul {
    justify-content: flex-start;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
    scroll-behavior: auto;
  }

  /* The universal rule above can't override `html { scroll-behavior:
     smooth }` (higher specificity), so anchor/hash jumps would still
     animate. Reset the scroll container explicitly. */
  html {
    scroll-behavior: auto;
  }
}

/* ─────────────────────────────────────────────────────────────────────
   Dot grid spotlight

   Background atmosphere behind .hero and .references — one continuous
   layer that the white .value__pill punches through. Dependencies:

     • Host section MUST be position: relative (set below).
     • Section content (.container) is lifted to z-index: 1 so it
       paints above the .dgs layer (which sits at z-index: 0).
     • --mx{1,2,3} / --my{1,2,3} are written by
       scripts/dot-grid-spotlight.js — three spotlights drifting via
       a sum-of-sines path (no cursor coupling). CSS fallbacks below
       cover the pre-JS frame and the reduced-motion path.
     • Per-variant fade stops are tunable via --dgs-fade-start /
       --dgs-fade-end without touching JS.
   ───────────────────────────────────────────────────────────────────── */

.hero,
.references {
  position: relative;
}

.hero > .container,
.references > .container {
  position: relative;
  z-index: 1;
}

/* Lift the contact bars above the .dgs layer in their host section.
   .utility--top now lives inside .hero (under the bio); .utility--bottom
   is the page footer with .dgs--footer reaching down behind it. */
.utility--top,
.utility--bottom {
  position: relative;
  z-index: 1;
}

.dgs {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;

  /* Three drifting spotlights. JS animates these per frame; the CSS
     defaults give a sensible static composition for the pre-JS frame
     and the reduced-motion path. */
  --mx1: 30%;
  --my1: 35%;
  --mx2: 70%;
  --my2: 50%;
  --mx3: 50%;
  --my3: 75%;

  --dgs-fade-start: 70%;
  --dgs-fade-end: 100%;
  --dgs-fade-direction: to bottom;

  opacity: 1;
  transition: opacity 600ms ease-out;
}

.dgs--hero {
  --dgs-fade-direction: to bottom;
  --dgs-fade-start: 70%;
  --dgs-fade-end: 100%;
}

.dgs--footer {
  --dgs-fade-direction: to top;
  --dgs-fade-start: 70%;
  --dgs-fade-end: 100%;

  /* Reach downward past the references' bottom edge so the dots pass
     behind the .utility--bottom contact bar. The bottom bar uses a
     slightly taller padding (28px / 36px) than the top, so we bump
     the overflow accordingly. */
  bottom: -100px;
}

.dgs__fade {
  position: absolute;
  inset: 0;
  -webkit-mask-image: linear-gradient(
    var(--dgs-fade-direction),
    black 0%,
    black var(--dgs-fade-start),
    transparent var(--dgs-fade-end)
  );
  mask-image: linear-gradient(
    var(--dgs-fade-direction),
    black 0%,
    black var(--dgs-fade-start),
    transparent var(--dgs-fade-end)
  );
}

.dgs__glow {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(
      420px circle at var(--mx1) var(--my1),
      var(--glow),
      transparent 55%
    ),
    radial-gradient(
      420px circle at var(--mx2) var(--my2),
      var(--glow),
      transparent 55%
    ),
    radial-gradient(
      420px circle at var(--mx3) var(--my3),
      var(--glow),
      transparent 55%
    );
}

.dgs__dots {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(
    rgba(255, 255, 255, 0.14) 1px,
    transparent 1px
  );
  background-size: 22px 22px;

  /* Three stacked spotlight masks. Compositing is additive so overlapping
     spotlights brighten naturally rather than cancelling each other. */
  -webkit-mask:
    radial-gradient(
      380px circle at var(--mx1) var(--my1),
      black 10%,
      transparent 70%
    ),
    radial-gradient(
      380px circle at var(--mx2) var(--my2),
      black 10%,
      transparent 70%
    ),
    radial-gradient(
      380px circle at var(--mx3) var(--my3),
      black 10%,
      transparent 70%
    );
  -webkit-mask-composite: source-over;
  mask:
    radial-gradient(
      380px circle at var(--mx1) var(--my1),
      black 10%,
      transparent 70%
    ),
    radial-gradient(
      380px circle at var(--mx2) var(--my2),
      black 10%,
      transparent 70%
    ),
    radial-gradient(
      380px circle at var(--mx3) var(--my3),
      black 10%,
      transparent 70%
    );
  mask-composite: add;
}

/* ─────────────────────────────────────────────────────────────────────
   Case study surface

   Long-form, white editorial surface. Re-uses the global typography,
   focus rings, and .utility--bottom footer by overriding the surface
   tokens. The .case-study class applies on either <body> (the standalone
   fallback page) or any wrapper inside the modal dialog, so all
   descendant rules below work in both contexts.
   ───────────────────────────────────────────────────────────────────── */

.case-study {
  --bg: #ffffff;
  --fg: #0d0d0d;
  --fg-dim: rgba(13, 13, 13, 0.65);
  --fg-faint: rgba(13, 13, 13, 0.45);

  background: var(--bg);
  color: var(--fg);
}

.case-study__topbar {
  padding-block: clamp(20px, 3vw, 32px);
}

.case-study__back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-small);
  letter-spacing: 0.02em;
  color: var(--fg-dim);
  text-decoration: none;
  transition: color 200ms ease;
}

.case-study__back:hover,
.case-study__back:focus-visible {
  color: var(--fg);
}

.case-study__back:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.case-study__article {
  max-width: 680px;
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block: clamp(32px, 6vw, 72px) clamp(48px, 8vw, 96px);
}

.case-study__header {
  margin-bottom: clamp(48px, 6vw, 80px);
}

.case-study__eyebrow {
  font-size: var(--fs-small);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 16px;
}

.case-study__title {
  font-size: var(--fs-display);
  letter-spacing: -0.01em;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 0.3em;
  row-gap: 0.5em;
}

.case-study__title-logo {
  display: inline-block;
  width: auto;
  height: 0.7em;
  vertical-align: baseline;
}

.case-study__divider {
  margin-block: clamp(48px, 6vw, 80px);
  border-top: 1px solid rgba(13, 13, 13, 0.1);
}

/* Inline brand wordmarks inside body copy — sized to sit in the flow
   of the text at --fs-body. Use `em` sizing so they track font scaling. */
.case-study__inline-logo {
  display: inline-block;
  width: auto;
  height: 0.9em;
  vertical-align: -0.08em;
  margin-inline: 0.1em;
}

.case-study__inline-logo--framer {
  /* framer.svg uses fill="currentColor"; inherit body text colour */
  color: var(--fg);
  height: 0.95em;
}

.case-study__inline-logo--eon {
  /* eon.svg carries its own brand red via its own fill attribute */
  height: 0.85em;
}

/* Pills wrapping inline brand wordmarks inside body copy. The img is
   force-whitened via a filter so the SVGs render legibly on the brand-
   coloured pill regardless of their original fill. Shared between
   homepage cards and case-study prose. */
.logo-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.25em 0.5em;
  border-radius: 0.35em;
  line-height: 1;
  vertical-align: middle;
  margin-inline: 0.1em;
}

.logo-pill > img {
  display: inline-block;
  width: auto;
  height: 0.85em;
  vertical-align: baseline;
  margin-inline: 0;
  filter: brightness(0) invert(1);
}

.logo-pill--eon {
  background: #ea1b0a;
}

.logo-pill--framer {
  background: #000000;
}

.logo-pill--webflow {
  background: #146ef5;
}

.case-study__intro {
  margin-top: clamp(20px, 3vw, 28px);
  font-weight: 400;
  font-size: clamp(18px, 1.6vw, 22px);
  color: var(--fg-dim);
}

.case-study__section {
  margin-top: clamp(48px, 6vw, 80px);
}

.case-study__section h2 {
  font-size: var(--fs-h2);
  margin-bottom: clamp(16px, 2vw, 24px);
}

.case-study__section h3 {
  font-size: var(--fs-h3);
  margin-top: clamp(36px, 4.5vw, 56px);
  margin-bottom: clamp(8px, 1vw, 12px);
  line-height: var(--lh-snug);
}

.case-study__section p {
  font-weight: 300;
}

.case-study__section p + p {
  margin-top: 1em;
}

/* Inline highlights that call out the two cuts of PP Fragment by
   showing each in body copy. ExtraBold of each family is the only
   non-Regular static cut available, and the visual emphasis it gives
   matches the role these spans play in the prose. */
.case-study__text-highlight--glare {
  font-family: "PP Fragment Glare";
  font-weight: 800;
}

.case-study__text-highlight--sans {
  font-family: "PP Fragment Sans";
  font-weight: 800;
}

.case-study__figure--center {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* 660px must match podcast iframe max-width in case-studies/ai-workflows.html */
.case-study__figure--center .case-study__figure-label {
  align-self: center;
  box-sizing: border-box;
  width: 100%;
  max-width: 660px;
  text-align: start;
}

.case-study__figure {
  margin: clamp(48px, 7vw, 80px) 0;
}

.case-study__figure-frame {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  border: 1px dashed rgba(13, 13, 13, 0.25);
  border-radius: var(--radius-card);
  background: rgba(13, 13, 13, 0.02);
  padding: 24px;
  text-align: center;
}

.case-study__figure-label {
  margin-top: 16px;
  padding-inline: clamp(20px, 2.4vw, 32px);
  font-size: var(--fs-small);
  letter-spacing: 0.02em;
  color: var(--fg-faint);
}

/* Visual placeholder used inside in-progress case studies in place of an
   figure-panel or case-grid. Sits in the same figure slot so layout
   stays stable when the real visual is dropped in later. */
.case-study__placeholder {
  display: grid;
  place-items: center;
  min-height: clamp(180px, 22vw, 280px);
  padding: clamp(20px, 2.4vw, 32px);
  border: 1px dashed rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-card);
  background:
    repeating-linear-gradient(
      135deg,
      rgba(255, 255, 255, 0.018) 0,
      rgba(255, 255, 255, 0.018) 10px,
      transparent 10px,
      transparent 20px
    ),
    #141414;
  color: rgba(255, 255, 255, 0.5);
  text-align: center;
}

.case-study__placeholder p {
  margin: 0;
  font-size: var(--fs-small);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.case-study__placeholder em {
  display: block;
  margin-top: 8px;
  font-style: normal;
  letter-spacing: 0.02em;
  text-transform: none;
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.95rem;
}

/* Font morph wordmark — fills the placeholder slot and animates its
   font-variation-settings + colour from JS (scripts/visuals/font-morph.js).
   The mid-state baked in here is what reduced-motion / first paint resolves
   to, so the slot never appears as a flat sans or glare flash before JS
   takes over. */
.case-study__placeholder--font-morph {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(24px, 5vw, 64px) 24px;
  background: var(--accent);
  border: 1px solid rgba(13, 13, 13, 0.08);
}

.case-study__placeholder--font-morph .font-morph__word {
  font-family: "PP Fragment VF", serif;
  font-size: clamp(48px, 9vw, 140px);
  line-height: 0.95;
  letter-spacing: -0.02em;
  color: rgba(255, 255, 255, 0.75);
  font-variation-settings:
    "wght" 545,
    "SRFF" 25,
    "ital" 50;
}

.case-study__embed {
  border-radius: var(--radius-card);
  overflow: hidden;
  background: #fff;
  border: 1px solid rgba(13, 13, 13, 0.08);
}

.case-study__embed-frame {
  display: block;
  width: 100%;
  height: 720px;
  border: 0;
}

/* ─────────────────────────────────────────────────────────────────────
   Case study — wide figure breakout
   Pulls a figure out past the 680px article column on desktop
   and collapses back in line once the viewport can't support the
   wider width. No media queries needed.
   ───────────────────────────────────────────────────────────────────── */

.case-study__figure--wide {
  --wide-width: min(1040px, calc(100vw - var(--gutter) * 2));
  width: var(--wide-width);
  margin-inline: calc((100% - var(--wide-width)) / 2);
}

.case-study--trail .case-study__figure--wide {
  --wide-width: min(700px, calc(100vw - var(--gutter) * 2));
}

/* ─────────────────────────────────────────────────────────────────────
   Case study — screenshot grid
   Masonry-style CSS grid with tile #4 as the hero (col-span 4 × row-span 2).
   Collapses to 2 cols on tablet and 1 col on mobile.
   ───────────────────────────────────────────────────────────────────── */

.case-grid-figure {
  margin-block: clamp(48px, 7vw, 80px);
}

.case-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(10px, 1.2vw, 16px);
  margin-bottom: clamp(12px, 1.4vw, 20px);
}

.case-grid__item {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-media);
  border: 1px solid rgba(13, 13, 13, 0.08);
  background: #fff;
  aspect-ratio: 4 / 3;
}

.case-grid__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
}

.case-grid__row-2 {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 22fr 27fr;
  gap: clamp(10px, 1.2vw, 16px);
}

.case-grid__stack {
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.2vw, 16px);
}

.case-grid__item--4 {
  aspect-ratio: 4 / 5;
}

@media (max-width: 560px) {
  .case-grid,
  .case-grid__row-2 {
    grid-template-columns: 1fr;
  }
  .case-grid__item,
  .case-grid__item--4 {
    aspect-ratio: 16 / 10;
  }
}

/* ─────────────────────────────────────────────────────────────────────
   Case grid — natural ratio variant
   Lets the image dictate item height (intrinsic aspect ratio comes
   from the <img>'s width/height attributes via `aspect-ratio: auto`)
   instead of being cropped by `object-fit: cover`. Used by the
   design-system case study where the design IS the content and
   shouldn't be cut off.
   ───────────────────────────────────────────────────────────────────── */

.case-grid__item--natural {
  aspect-ratio: auto;
}

.case-grid__item--natural img {
  width: 100%;
  height: auto;
  object-fit: contain;
  object-position: center;
}

/* ─────────────────────────────────────────────────────────────────────
   Case grid — design-system variants

   Four bespoke layouts built on the shared `.case-grid` base. Each one
   uses `align-items: start` (or a flex column) so items keep their
   natural ratios without being stretched. All collapse to a single
   column at the existing 560px breakpoint.
   ───────────────────────────────────────────────────────────────────── */

.case-grid__col {
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.2vw, 16px);
  min-width: 0;
}

/* Problem grid — left column stacks two landscape shots, right column
   holds the tall one-pager spanning both rows. */
.case-grid--problem {
  grid-template-columns: 1.6fr 1fr;
  align-items: start;
}

.case-grid--problem .case-grid__tall {
  align-self: stretch;
}

.case-grid--problem .case-grid__tall img {
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* System grid — full-width hero (component library) over a 2×2 of
   product shots. */
.case-grid--system {
  grid-template-columns: 1fr 1fr;
  align-items: start;
}

.case-grid--system .case-grid__hero {
  grid-column: 1 / -1;
}

/* Marketing grid — tall event banner on the left, vertical stack of
   booklet → items → cards on the right. The banner uses `cover` so
   it stretches to match the right column's height. */
.case-grid--marketing {
  grid-template-columns: 1fr 1.6fr;
  align-items: stretch;
}

/* Banner cell takes the image's intrinsic ratio, so the row height is
   the banner's natural height — no crop. */
.case-grid--marketing .case-grid__banner {
  aspect-ratio: 800 / 2011;
}

.case-grid--marketing .case-grid__banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Right column stretches to the banner height and distributes its
   children proportionally so the bottom edges of both columns align.
   Desktop only — on mobile the grid collapses to a single column where
   `.case-grid__col` has no defined height and `flex: 1 1 0` children
   would collapse to zero. */
@media (min-width: 561px) {
  .case-grid--marketing .case-grid__col {
    height: 100%;
  }

  .case-grid--marketing .case-grid__col > .case-grid__item--natural {
    flex: 1 1 0;
    min-height: 0;
    aspect-ratio: auto;
  }

  .case-grid--marketing .case-grid__col > .case-grid__item--natural img {
    height: 100%;
    object-fit: cover;
  }

  .case-grid--marketing .case-grid__cards {
    flex: 0.5 1 0;
    min-height: 0;
  }

  .case-grid--marketing .case-grid__cards .case-grid__item--natural img {
    height: 100%;
    object-fit: cover;
  }
}

.case-grid__cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(10px, 1.2vw, 16px);
}

/* Open Graph grid — 2×2 of wide social cards at their native ~1.9:1. */
.case-grid--og {
  grid-template-columns: 1fr 1fr;
  align-items: start;
}

@media (max-width: 560px) {
  .case-grid--problem,
  .case-grid--system,
  .case-grid--marketing,
  .case-grid--og,
  .case-grid__cards {
    grid-template-columns: 1fr;
  }
  .case-grid--marketing .case-grid__banner {
    /* Avoid the 2000px-tall banner dominating the mobile layout —
       crop to a more sensible portrait letterbox at narrow widths. */
    aspect-ratio: 4 / 5;
  }
}

/* ─────────────────────────────────────────────────────────────────────
   Analytics panel
   Editorial take on Search Console: hairline card, metric tiles,
   two SVG line charts, and a pages indicator strip. All SVGs are
   inline in the HTML and styled via the classes below.
   ───────────────────────────────────────────────────────────────────── */

.figure-panel {
  --panel-line: rgba(255, 255, 255, 0.1);
  --panel-grid: rgba(255, 255, 255, 0.08);
  --panel-surface: #141414;

  --fg: #ffffff;
  --fg-dim: rgba(255, 255, 255, 0.65);
  --fg-faint: rgba(255, 255, 255, 0.45);

  background: var(--panel-surface);
  border: 1px solid var(--panel-line);
  border-radius: var(--radius-card);
  padding: clamp(20px, 2.4vw, 32px);
  display: grid;
  gap: clamp(20px, 2.4vw, 28px);
}

.figure-panel__header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px 24px;
  padding-bottom: clamp(14px, 1.6vw, 20px);
  border-bottom: 1px solid var(--panel-line);
}

.figure-panel__eyebrow {
  font-size: var(--fs-small);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0;
}

.figure-panel__tabs {
  display: inline-flex;
  gap: 16px;
  font-size: var(--fs-small);
  color: var(--fg-faint);
  list-style: none;
  padding: 0;
  margin: 0;
}

.figure-panel__tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.figure-panel__tab::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--fg-faint);
  display: inline-block;
}

.figure-panel__tab--accent::before {
  background: var(--accent);
}

.figure-panel__tab--muted::before {
  background: rgba(255, 255, 255, 0.35);
}

.figure-panel__tiles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 28px);
}

@media (max-width: 540px) {
  .figure-panel__tiles {
    grid-template-columns: 1fr;
  }
}

.figure-panel__tile {
  display: grid;
  gap: 10px;
  padding: clamp(14px, 1.6vw, 20px);
  border: 1px solid var(--panel-line);
  border-radius: calc(var(--radius-card) - 8px);
  background: rgba(255, 255, 255, 0.04);
}

.figure-panel__tile-label {
  font-size: var(--fs-small);
  letter-spacing: 0.04em;
  color: var(--fg-faint);
}

.figure-panel__tile-value {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(36px, 4.4vw, 56px);
  line-height: 1;
  color: var(--fg);
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}

.figure-panel__tile-unit {
  font-family: var(--font-text);
  font-size: var(--fs-small);
  color: var(--fg-faint);
  letter-spacing: 0.02em;
}

.figure-panel__tile-delta {
  font-size: var(--fs-small);
  color: var(--fg-dim);
}

.figure-panel__spark {
  display: block;
  width: 100%;
  height: 28px;
  overflow: visible;
}

.figure-panel__spark-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.figure-panel__spark-area {
  fill: var(--accent);
  opacity: 0.18;
  stroke: none;
}

.figure-panel__charts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 28px);
}

@media (max-width: 720px) {
  .figure-panel__charts {
    grid-template-columns: 1fr;
  }
}

.figure-panel__chart-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.figure-panel__chart-title {
  font-size: var(--fs-small);
  letter-spacing: 0.04em;
  color: var(--fg);
  margin: 0;
}

.figure-panel__chart-range {
  font-size: var(--fs-small);
  color: var(--fg-faint);
}

.figure-panel__chart-svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.figure-panel__chart-grid {
  stroke: var(--panel-grid);
  stroke-width: 1;
  fill: none;
}

.figure-panel__chart-area {
  fill: var(--accent);
  opacity: 0.18;
  stroke: none;
}

.figure-panel__chart-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.figure-panel__chart-axis {
  font-family: var(--font-text);
  font-size: 11px;
  letter-spacing: 0.04em;
  fill: var(--fg-faint);
}

.figure-panel--single .figure-panel__chart-axis {
  font-size: 9px;
}

.figure-panel__chart-dots {
  fill: var(--accent);
  stroke: var(--panel-surface);
  stroke-width: 1.5;
}

.figure-panel__chart-annotation {
  font-family: var(--font-text);
  font-size: 6px;
  letter-spacing: 0.04em;
  fill: var(--fg-dim);
  paint-order: stroke fill;
  stroke: var(--panel-surface);
  stroke-width: 3;
  stroke-linejoin: round;
}

.figure-panel__pages {
  display: grid;
  gap: 10px;
  padding-top: clamp(14px, 1.6vw, 20px);
  border-top: 1px solid var(--panel-line);
}

.figure-panel__pages-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-size: var(--fs-small);
  color: var(--fg-faint);
  letter-spacing: 0.04em;
}

.figure-panel__pages-count {
  color: var(--fg);
  font-family: var(--font-display);
  font-size: clamp(18px, 1.6vw, 22px);
}

.figure-panel__pages-bars {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.figure-panel__pages-bar {
  width: 6px;
  height: 16px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.22);
}

.figure-panel__pages-bar--accent {
  background: var(--accent);
  opacity: 0.9;
}

/* ─────────────────────────────────────────────────────────────────────
   Commit heatmap — GitHub-style 7×N grid that lives inside an
   figure-panel shell. Mount point markup is just an empty
   `.commit-heatmap[data-commit-heatmap]`; commit-heatmap.js fills in
   the inner structure (months / days / grid / footer).
   ───────────────────────────────────────────────────────────────────── */

.commit-heatmap {
  --hm-gap: 3px;
  --hm-label: 9px;
  --hm-empty: rgba(255, 255, 255, 0.06);

  color: var(--fg-faint);
  /* Defensive clip: the surrounding .figure-panel doesn't enforce
     overflow, so any sub-pixel rounding inside the heatmap would
     visibly escape the panel's rounded corners. Containing it here
     guarantees the chart always sits inside the surface. */
  overflow: hidden;
}

.commit-heatmap__inner {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto auto;
  column-gap: 8px;
  row-gap: 6px;
  min-width: 0;
}

/* Daily view container collapses into the parent grid so the existing
   layout (months / days / grid in a 2-column auto-1fr grid) is unchanged
   on desktop. The wrapper exists purely so the @media swap can hide
   the whole daily view in one selector. */
.commit-heatmap__daily {
  display: contents;
}

.commit-heatmap__months {
  grid-column: 2;
  grid-row: 1;
  display: grid;
  grid-template-columns: repeat(var(--hm-weeks, 53), 1fr);
  column-gap: var(--hm-gap);
  font-size: var(--hm-label);
  letter-spacing: 0.02em;
  color: var(--fg-faint);
}

.commit-heatmap__month {
  white-space: nowrap;
}

.commit-heatmap__days {
  grid-column: 1;
  grid-row: 2;
  display: grid;
  grid-template-rows: repeat(7, 1fr);
  font-size: var(--hm-label);
  letter-spacing: 0.02em;
  color: var(--fg-faint);
  align-items: center;
}

.commit-heatmap__day {
  line-height: 1;
}

/* Daily grid fills the available panel width via 1fr columns. Row
   height is driven by each cell's `aspect-ratio: 1` (auto rows take the
   intrinsic cell size = column-track width), so columns and rows always
   resolve to the same value and cells can never overshoot their track. */
.commit-heatmap__grid {
  grid-column: 2;
  grid-row: 2;
  display: grid;
  grid-template-columns: repeat(var(--hm-weeks, 53), 1fr);
  grid-template-rows: repeat(7, auto);
  grid-auto-flow: column;
  gap: var(--hm-gap);
  width: 100%;
  min-width: 0;
}

.commit-heatmap__cell {
  border-radius: 2px;
  background: var(--hm-empty);
  aspect-ratio: 1;
  min-width: 0;
  min-height: 0;
}

/* Quartile-ish intensity ramp anchored on --accent. color-mix lets the
   accent variable stay the single source of truth — change it in the
   :root and the heatmap re-tones automatically. */
.commit-heatmap__cell[data-level="1"],
.commit-heatmap__stripe[data-level="1"] .commit-heatmap__stripe-fill {
  background: color-mix(in oklab, var(--accent) 30%, var(--panel-surface));
}
.commit-heatmap__cell[data-level="2"],
.commit-heatmap__stripe[data-level="2"] .commit-heatmap__stripe-fill {
  background: color-mix(in oklab, var(--accent) 55%, var(--panel-surface));
}
.commit-heatmap__cell[data-level="3"],
.commit-heatmap__stripe[data-level="3"] .commit-heatmap__stripe-fill {
  background: color-mix(in oklab, var(--accent) 78%, var(--panel-surface));
}
.commit-heatmap__cell[data-level="4"],
.commit-heatmap__stripe[data-level="4"] .commit-heatmap__stripe-fill {
  background: var(--accent);
}

.commit-heatmap__cell[data-empty="true"] {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--hm-empty);
}

/* ── Monthly stripe view (mobile-only) ───────────────────────────── */

.commit-heatmap__monthly {
  display: none;
  grid-column: 1 / -1;
  grid-row: 2;
  grid-template-columns: repeat(12, 1fr);
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.commit-heatmap__stripe {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  min-width: 0;
}

.commit-heatmap__stripe-fill {
  height: 56px;
  border-radius: 4px;
  background: var(--hm-empty);
}

.commit-heatmap__stripe-label {
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--fg-faint);
  text-align: center;
  text-transform: uppercase;
}

@media (max-width: 640px) {
  .commit-heatmap__inner {
    max-width: none;
  }
  .commit-heatmap__daily {
    display: none;
  }
  .commit-heatmap__monthly {
    display: grid;
  }
}

.commit-heatmap__footer {
  grid-column: 1 / -1;
  grid-row: 3;
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 16px;
  font-size: var(--fs-small);
  color: var(--fg-faint);
}

.commit-heatmap__total {
  letter-spacing: 0.02em;
}

.commit-heatmap__legend {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.commit-heatmap__cell--legend {
  width: 10px;
  height: 10px;
  aspect-ratio: 1;
}

/* ─────────────────────────────────────────────────────────────────────
   Linear cycle snapshot — three-row recap of upcoming / current /
   completed cycles. Lives inside an .figure-panel shell, mirrors
   the Linear cycles UI: status icon, name, pill, capacity ring, scope
   stats, and (current row only) an embedded SVG burnup chart with
   Scope / Ideal / Started / Completed series.
   ───────────────────────────────────────────────────────────────────── */

.linear-snapshot {
  /* Single new colour for the Started series — yellow that sits next
     to --accent without competing for attention. Everything else
     reuses the figure-panel's --fg / --fg-faint / --accent tokens. */
  --ls-warm: #f5c542;
  --ls-grid: rgba(255, 255, 255, 0.07);
  --ls-line-faint: rgba(255, 255, 255, 0.55);
  --ls-rule: rgba(255, 255, 255, 0.08);
  --ls-pill-bg: rgba(255, 255, 255, 0.07);

  display: grid;
  gap: 0;
}

.linear-snapshot__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
}

.linear-snapshot__cycle {
  display: grid;
  gap: clamp(14px, 1.6vw, 20px);
  padding: clamp(16px, 2vw, 22px) 0;
  border-top: 1px solid var(--ls-rule);
}

.linear-snapshot__cycle:first-child {
  border-top: 0;
  padding-top: 0;
}

.linear-snapshot__cycle:last-child {
  padding-bottom: 0;
}

.linear-snapshot__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px 24px;
}

.linear-snapshot__head {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.linear-snapshot__icon {
  display: inline-flex;
  width: 14px;
  height: 14px;
  color: var(--fg-faint);
}

.linear-snapshot__icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

.linear-snapshot__icon--current {
  color: var(--accent);
}

.linear-snapshot__icon--done {
  color: var(--fg-dim);
}

.linear-snapshot__name {
  font-size: var(--fs-small);
  color: var(--fg);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.linear-snapshot__pill {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--fg-dim);
  background: var(--ls-pill-bg);
  border-radius: 999px;
  padding: 3px 9px;
  white-space: nowrap;
}

.linear-snapshot__pill--current {
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 16%, transparent);
}

.linear-snapshot__stats {
  display: inline-flex;
  align-items: center;
  gap: clamp(14px, 2vw, 28px);
  flex-wrap: wrap;
  justify-content: flex-end;
}

.linear-snapshot__stat {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.linear-snapshot__ring {
  width: 28px;
  height: 28px;
  display: block;
  flex-shrink: 0;
  color: var(--fg-faint);
}

.linear-snapshot__ring--accent {
  color: var(--accent);
}

.linear-snapshot__stat-text {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.linear-snapshot__stat-value {
  font-size: var(--fs-small);
  color: var(--fg);
  letter-spacing: 0.02em;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
}

.linear-snapshot__stat-bump {
  font-style: normal;
  font-size: 10px;
  color: var(--ls-warm);
  letter-spacing: 0.04em;
}

.linear-snapshot__stat-sub {
  font-size: var(--fs-small);
  color: var(--fg-faint);
  letter-spacing: 0.02em;
}

/* ── Burnup chart (current cycle only) ─────────────────────────── */

.linear-snapshot__chart {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: stretch;
  gap: clamp(16px, 2vw, 28px);
  padding: clamp(8px, 1.2vw, 14px) 0 0;
}

.linear-snapshot__chart-svg {
  display: block;
  width: 100%;
  height: auto;
  min-width: 0;
  overflow: visible;
}

.linear-snapshot__chart-grid line {
  stroke: var(--ls-grid);
  stroke-width: 1;
  fill: none;
}

.linear-snapshot__chart-axis text {
  font-family: var(--font-text);
  font-size: 9px;
  letter-spacing: 0.06em;
  fill: var(--fg-faint);
  text-transform: uppercase;
}

.linear-snapshot__chart-scope {
  fill: none;
  stroke: var(--ls-line-faint);
  stroke-width: 1.25;
  stroke-linejoin: round;
}

.linear-snapshot__chart-ideal {
  stroke: var(--accent);
  stroke-width: 1.25;
  stroke-dasharray: 3 4;
  stroke-linecap: round;
  opacity: 0.5;
  fill: none;
}

.linear-snapshot__chart-started-area {
  fill: url(#ls-started-fill);
  stroke: none;
}

.linear-snapshot__chart-started-line {
  fill: none;
  stroke: var(--ls-warm);
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.linear-snapshot__chart-completed-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.linear-snapshot__chart-today {
  stroke: rgba(255, 255, 255, 0.18);
  stroke-width: 1;
  stroke-dasharray: 2 3;
}

.linear-snapshot__chart-dot {
  stroke: var(--panel-surface);
  stroke-width: 1.5;
}

.linear-snapshot__chart-dot--started {
  fill: var(--ls-warm);
}

.linear-snapshot__chart-dot--completed {
  fill: var(--accent);
}

/* ── Legend (sits to the right of the chart on desktop) ─────────── */

.linear-snapshot__legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  align-content: center;
  gap: 10px;
  min-width: 140px;
}

.linear-snapshot__legend-row {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-small);
  color: var(--fg-faint);
}

.linear-snapshot__legend-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
}

.linear-snapshot__legend-swatch--scope {
  background: var(--ls-line-faint);
}

.linear-snapshot__legend-swatch--started {
  background: var(--ls-warm);
}

.linear-snapshot__legend-swatch--completed {
  background: var(--accent);
}

.linear-snapshot__legend-label {
  color: var(--fg-faint);
  letter-spacing: 0.02em;
}

.linear-snapshot__legend-value {
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}

.linear-snapshot__legend-value em {
  font-style: normal;
  color: var(--fg-faint);
}

.linear-snapshot__legend-bump {
  color: var(--ls-warm) !important;
  font-style: normal;
}

/* ── Responsive ─────────────────────────────────────────────────── */

/* Tablet: stats wrap below name/pill, chart legend drops under chart */
@media (max-width: 720px) {
  .linear-snapshot__row {
    grid-template-columns: minmax(0, 1fr);
  }

  .linear-snapshot__stats {
    justify-content: flex-start;
  }

  .linear-snapshot__chart {
    grid-template-columns: minmax(0, 1fr);
  }

  .linear-snapshot__legend {
    min-width: 0;
    grid-auto-flow: row;
  }
}

/* Small tablet / large phone: trim ring + drop the middle date label */
@media (max-width: 520px) {
  .linear-snapshot__ring {
    width: 22px;
    height: 22px;
  }

  /* SVG <text> elements honour `display:none` in all modern engines,
     but belt-and-braces with `visibility:hidden` ensures the middle
     date stays out of the way even if a future engine quirk regresses
     the display behaviour for SVG. */
  .linear-snapshot__chart-axis-mid {
    display: none;
    visibility: hidden;
  }

  .linear-snapshot__chart-axis text {
    font-size: 10px;
  }
}

/* Phone: stack stats vertically so each stat keeps its ring + label
   together rather than wrapping awkwardly mid-line */
@media (max-width: 420px) {
  .linear-snapshot__stats {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }

  .linear-snapshot__legend-row {
    grid-template-columns: 10px minmax(0, 1fr) auto;
  }
}

.case-study__footer {
  margin-top: clamp(56px, 8vw, 96px);
  padding-top: clamp(24px, 3vw, 32px);
  border-top: 1px solid rgba(13, 13, 13, 0.1);
}

.case-study .utility__nav a {
  color: var(--fg-dim);
}

.case-study .utility__nav a:hover,
.case-study .utility__nav a:focus-visible {
  color: var(--fg);
}

.case-study .util-icon {
  /* The icons in /assets are white SVGs designed for the dark home page;
     invert them on the white case-study surface so they read correctly. */
  filter: invert(1);
}

/* ─────────────────────────────────────────────────────────────────────
   Case study modal — card-as-source morph

   A separate fixed layer (`.case-modal`) sits above the page. JS runs
   a FLIP from the clicked card's rect to fullscreen on open, and back
   to the same card's rect on close. The page underneath is frozen via
   `html.is-modal-locked` + `inert` on the modal's siblings, so the
   close FLIP measures the same rect we opened from regardless of any
   focus moves or browser UI nudges while open.
   ───────────────────────────────────────────────────────────────────── */

.case-modal {
  position: fixed;
  inset: 0;
  background: var(--pill-bg);
  z-index: 100;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Anchor JS-driven FLIP transforms to the top-left corner so
     `scale(sx, sy)` + `translate(dx, dy)` reproduces the card's source
     rect. `will-change` pins a composited layer for the entire open
     lifetime so the first scroll inside the modal doesn't race the
     FLIP runner's per-animation `willChange` teardown. */
  transform-origin: 0 0;
  will-change: transform;
  /* Override the document-level `scroll-behavior: smooth` for this
     surface — switchCase() resets `modalEl.scrollTop = 0` and a
     smooth-scroll animation would race the FLIP and read as a jump. */
  scroll-behavior: auto;
}

.case-modal[hidden] {
  display: none;
}

.case-modal .case-study__article {
  padding-block: clamp(72px, 9vw, 112px) clamp(48px, 8vw, 96px);
}

.case-close {
  position: fixed;
  top: 16px;
  right: 16px;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #0d0d0d;
  color: #ffffff;
  cursor: pointer;
  z-index: 110;
  box-shadow: var(--shadow-button);
  border: 1px solid var(--card-border);
  transition:
    background 200ms ease,
    border-color 200ms ease;
}

.case-close[hidden] {
  display: none;
}

.case-close:hover,
.case-close:focus-visible {
  background: #2a2a2a;
  border-color: rgba(255, 255, 255, 0.3);
}

.case-close:focus-visible {
  outline: 2px solid transparent;
  box-shadow: 0 0 0 3px var(--accent);
}

/* End-of-article close — quiet rule + centred dark pill that echoes the
   top close button so they read as the same action. */
.case-study__end {
  margin-top: clamp(56px, 8vw, 96px);
  padding-top: clamp(24px, 3vw, 32px);
  border-top: 1px solid rgba(13, 13, 13, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
}

.case-study__nav-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 999px;
  background: #0d0d0d;
  color: #ffffff;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-button);
  border: 1px solid var(--card-border);
  transition:
    background 200ms ease,
    border-color 200ms ease;
}

.case-study__nav-btn:hover {
  background: #2a2a2a;
  border-color: rgba(255, 255, 255, 0.3);
}

.case-study__nav-btn:focus-visible {
  outline: 2px solid transparent;
  border-radius: 999px;
  box-shadow: 0 0 0 3px var(--accent);
}

.case-study__close-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 20px;
  border: 1px solid var(--card-border);
  border-radius: 999px;
  background: #0d0d0d;
  color: #ffffff;
  box-shadow: var(--shadow-button);
  font-family: var(--font-text);
  font-size: var(--fs-small);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  transition:
    background 200ms ease,
    border-color 200ms ease;
}

.case-study__close-btn:hover {
  background: #2a2a2a;
  border-color: rgba(255, 255, 255, 0.3);
}

.case-study__close-btn:focus-visible {
  outline: 2px solid transparent;
  box-shadow: 0 0 0 3px var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .case-close {
    transition: none;
  }
}

/* ─── Design system foundations + iconography (case-study modal) ────────
   Two visuals on the design-system case study, sharing a light-card
   wrapper that mirrors the .case-study__embed treatment used by the
   end-to-end email prototype. The foundations card stacks three
   panels (colour ramps, type scale, radius tiles); the iconography
   card stacks three zoom levels of the Iconic library.

   Both are mounted by JS and stagger in via per-step `.is-in` flips —
   the CSS just declares the start vs end visual states. */

.ds-card {
  --ds-bg: #fafafa;
  --ds-border: rgba(13, 13, 13, 0.08);
  --ds-text: #39375b;
  --ds-text-dim: #50517c;
  --ds-text-faint: #8084a0;
  --ds-divider: rgba(13, 13, 13, 0.07);

  background: var(--ds-bg);
  border: 1px solid var(--ds-border);
  border-radius: var(--radius-card);
  padding: clamp(20px, 2.4vw, 32px);
  color: var(--ds-text);
  font-family: var(--font-text);
  box-sizing: border-box;
}

/* ── Foundations ─────────────────────────────────────────────────── */

.foundations {
  display: grid;
  row-gap: clamp(20px, 2.6vw, 32px);
}

.foundations__panel {
  display: grid;
  row-gap: 14px;
}

.foundations__panel + .foundations__panel {
  border-top: 1px solid var(--ds-divider);
  padding-top: clamp(20px, 2.6vw, 32px);
}

.foundations__heading,
.foundations__heading-row {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ds-text-faint);
}

.foundations__heading-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.foundations__heading-meta {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--ds-text-dim);
}

/* Colour ramps */

.foundations__ramps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  row-gap: 14px;
}

.foundations__ramp {
  display: grid;
  grid-template-columns: minmax(72px, 96px) 1fr;
  align-items: center;
  column-gap: clamp(12px, 2vw, 20px);
  row-gap: 4px;
}

.foundations__ramp-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.foundations__ramp-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--ds-text);
  letter-spacing: 0.01em;
}

.foundations__ramp-note {
  font-size: 10px;
  font-weight: 500;
  color: var(--ds-text-faint);
  letter-spacing: 0.02em;
}

.foundations__swatches {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 4px;
  min-width: 0;
}

.foundations__swatch {
  aspect-ratio: 1 / 1;
  border-radius: 5px;
  box-shadow: inset 0 0 0 1px rgba(13, 13, 13, 0.04);
}

.foundations__ramp-scale {
  grid-column: 2;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 4px;
  min-width: 0;
  margin-top: 2px;
}

.foundations__ramp-stop {
  font-size: 9px;
  font-weight: 500;
  color: var(--ds-text-faint);
  text-align: center;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}

/* Type scale */

.foundations__type-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  row-gap: 10px;
}

.foundations__type-row {
  display: grid;
  grid-template-columns: minmax(72px, 96px) 1fr auto;
  align-items: baseline;
  column-gap: clamp(12px, 2vw, 20px);
  min-height: 36px;
}

.foundations__type-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ds-text-faint);
}

.foundations__type-sample {
  font-family: "Hanken Grotesk", var(--font-text);
  color: var(--ds-text);
  line-height: 1.2;
  min-width: 0;
  /* Per-row size + weight set inline by foundations.js — fixed values
     so the panel renders at the real Evidenced scale even before the
     Hanken Grotesk webfont resolves. */
}

.foundations__type-meta {
  font-size: 11px;
  font-weight: 500;
  color: var(--ds-text-faint);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

/* Radius tiles */

.foundations__radius-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(12px, 2vw, 24px);
}

.foundations__radius-tile {
  display: grid;
  grid-template-rows: 56px auto auto;
  justify-items: center;
  align-items: center;
  gap: 6px 0;
  min-width: 0;
}

.foundations__radius-shape {
  grid-row: 1;
  align-self: center;
  justify-self: center;
  width: 56px;
  height: 56px;
  background: #e9e9ee;
  border: 1px solid #c6c9d7;
  box-sizing: border-box;
}

.foundations__radius-shape--pill {
  width: 80px;
  height: 32px;
}

.foundations__radius-shape--circle {
  border-radius: 50%;
}

.foundations__radius-label {
  grid-row: 2;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--ds-text);
}

.foundations__radius-meta {
  grid-row: 3;
  text-align: center;
  font-size: 10px;
  font-weight: 500;
  color: var(--ds-text-faint);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

/* Row / tile chrome — light card on each ramp, type row, radius tile. */

.foundations__ramp,
.foundations__type-row,
.foundations__radius-tile {
  background: #fff;
  border: 1px solid var(--ds-border);
  border-radius: 10px;
  box-sizing: border-box;
  padding: clamp(10px, 1.4vw, 14px) clamp(12px, 2vw, 18px);
}

/* Mobile — narrower panels, allow the colour scale labels to drop
   visual weight a touch since they get tight at small widths. */

@media (max-width: 640px) {
  .foundations__ramp {
    grid-template-columns: 1fr;
    row-gap: 6px;
  }

  .foundations__ramp-scale {
    grid-column: 1;
  }

  .foundations__type-row {
    grid-template-columns: minmax(56px, 72px) 1fr auto;
  }

  .foundations__type-meta {
    display: none;
  }

  .foundations__radius-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
  }

  .foundations__radius-tile {
    grid-template-rows: 44px auto auto;
  }

  .foundations__radius-shape {
    width: 44px;
    height: 44px;
  }

  .foundations__radius-shape--pill {
    width: 64px;
    height: 26px;
  }

  .foundations__swatches,
  .foundations__ramp-scale {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }

  .foundations__ramp-stop {
    font-size: 8px;
    letter-spacing: 0.02em;
  }
}

/* ── Iconography ─────────────────────────────────────────────────── */

.iconography {
  /* Single ink colour for every icon, regardless of stroke vs fill
     style — the disciplined palette is part of the point. */
  color: #39375b;
  display: grid;
  row-gap: clamp(20px, 2.6vw, 32px);
  text-align: center;
}

.iconography__hero {
  position: relative;
  display: grid;
  justify-items: center;
  padding: clamp(8px, 2vw, 24px) 0 clamp(12px, 2vw, 24px);
}

.iconography__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  color: inherit;
  background: transparent;
  border: none;
  padding: 2px;
}

.iconography__icon svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Stroke-only Iconic paths omit fill="none" in exports; SVG defaults
   fill to black and closed shapes read as solid slabs on the white
   cell. Only target elements with stroke and no fill attribute so
   intentional fill="currentColor" glyphs stay solid. */
.iconography__icon
  svg
  :is(path, circle, rect, line, polyline, polygon)[stroke]:not([fill]) {
  fill: none;
}

.iconography__icon--hero {
  width: clamp(80px, 15vw, 116px);
  height: clamp(80px, 15vw, 116px);
}

.iconography__icon--medium {
  width: clamp(36px, 4.4vw, 48px);
  height: clamp(36px, 4.4vw, 48px);
}

.iconography__icon--small {
  width: clamp(20px, 2.4vw, 24px);
  height: clamp(20px, 2.4vw, 24px);
}

/* Annotation labels — flank the hero, with a 1px leader. */

.iconography__annotations {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.iconography__annotation {
  display: flex;
  justify-content: center;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ds-text-faint);
}

.iconography__row,
.iconography__grid {
  list-style: none;
  margin: 0;
  padding: 0;
}

.iconography__row--medium {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: clamp(8px, 1.4vw, 16px);
}

.iconography__grid--small {
  display: grid;
  grid-template-columns: repeat(11, minmax(0, 1fr));
  gap: clamp(6px, 1vw, 12px);
}

.iconography__cell {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  background: #fff;
  border: 1px solid var(--ds-border);
}

.iconography__cell--medium {
  aspect-ratio: 1 / 1;
  border-radius: 8px;
}

.iconography__cell--small {
  aspect-ratio: 1 / 1;
  border-radius: 6px;
}

.iconography__cell--hero {
  width: clamp(104px, 20vw, 152px);
  height: clamp(104px, 20vw, 152px);
  border-radius: 8px;
}

@media (max-width: 640px) {
  .iconography__row--medium {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .iconography__grid--small {
    grid-template-columns: repeat(8, minmax(0, 1fr));
  }
}
