/* =============================================================================
   Zach Blevins — portfolio
   Design source: Figma "My-portfolio" (uC3h6oomQWHzwhk49XP16X), one page per
   breakpoint, built on "Portfolio-Design-System" (u7Woeg2PGHsjKAyaGKuKf2).
   Breakpoints match the designed frames: 390 (base) / 768 / 1200
   ========================================================================== */

/* ---------- Tokens ------------------------------------------------------- */

/* Colour — DS "Colors" collection, Light mode. Dark mode overrides the
   semantic layer below; the brand primitives never change. */
:root {
  color-scheme: light;

  --c-ink: #0e0f10;            /* Brand/Primary/1000 */
  --c-white: #ffffff;
  --c-accent: #d7ff38;         /* Brand/Secondary/50 */

  --c-bg: #ffffff;             /* Background/Default/Primary */
  --c-bg-hover: #f3f3f3;
  --c-text: #0e0f10;           /* Typography/Default/Primary */
  --c-text-2: rgba(14, 15, 16, 0.5);   /* …/Secondary */
  --c-text-3: rgba(14, 15, 16, 0.25);  /* …/Tertiary */
  --c-link: rgba(14, 15, 16, 0.9);     /* nav + inline links */
  --c-line: #e7e7e7;           /* Border/Default/Primary */
  --c-overlay: rgba(14, 15, 16, 0.05);        /* Opacity/050 — nav, tertiary buttons */
  --c-overlay-hover: rgba(14, 15, 16, 0.15);  /* Opacity/150 */
  --c-overlay-press: rgba(14, 15, 16, 0.1);   /* Opacity/100 */
  --c-field: #f3f3f3;

  /* Component colours — the DS binds these separately because they diverge
     from the text colours in Dark mode. */
  --c-btn-icon: #0e0f10;               /* Button/Icon/Tertiary + Ghost */
  --c-btn-primary: #0e0f10;            /* Button/Background/Primary */
  --c-btn-primary-hover: #0e0f10;
  --c-selector: #0e0f10;               /* nav underline — Button/Icon/Ghost */
  --c-caret: rgba(14, 15, 16, 0.25);   /* nav chevron — Opacity/250 */
  --c-menu-caret: #0e0f10;             /* mobile Work chevron — Icon/Brand/Tertiary */

  /* Surfaces that stay dark in both themes (footer, primary buttons) */
  --c-on-dark: rgba(255, 255, 255, 0.8);   /* Button/Typography/Primary */
  --c-on-dark-3: rgba(255, 255, 255, 0.2); /* White/200 — footer legal */

  /* Aliases kept for pages not yet moved to the new layout */
  --c-body: var(--c-text-2);
  --c-subtle: var(--c-text-3);
  --c-meta: #444444;
  --c-nav-bg: var(--c-overlay);

  /* Type families */
  --f-display: "Clash Display", "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
  --f-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --f-mono: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Layout — 390 frame */
  --page-max: 1440px;   /* widest a page's content (and a bleed figure) gets */
  --pad: 18px;
  --nav-pad-y: 18px;
  --nav-h: 76px;       /* 40px bar + padding */
  --hero-peek: 64px;
  --radius: 4px;

  --band-y: 40px;       /* block padding of a page band */
  --band-gap: 40px;     /* rhythm between items inside a band */
  --rule-space: 32px;   /* divider line → its label */

  --grid-gap: 32px;
  --grid-row-gap: 56px;
  --card-text-pr: 18px;  /* keeps card titles clear of the arrow */

  --footer-pad-y: 32px;
  --footer-gap: 32px;
  --footer-cta-gap: 24px;
  --cta-h: 48px;       /* medium button on phones and tablets */
  --cta-pad-x: 32px;
  --cta-icon: 22px;

  /* Legacy rhythm (about, process, contact, case studies) */
  --hero-gap: 24px;
  --section-gap: 24px;
  --rule-gap: 24px;

  /* Type — DS text styles as used on the 390 frames.
     Line heights follow the DS, which no longer sits on an 8px grid. */
  --fs-hero: 40px;   --lh-hero: 48px;   /* Title Header/Base (Title Hero above 390) */
  /* Page titles run a step above the hero token so they fill the first screen. */
  /* Page titles scale with the viewport: 44 at 390, 80 at 768, 96 at 1200,
     and on up to 125 once the content column stops growing. */
  --fs-page-title: max(40px, 11.28vw); --lh-page-title: 1.18;
  --lead-max: 768px;
  --fs-title: 32px;  --lh-title: 40px;  /* Title Header/Small */
  --fs-lead: 18px;   --lh-lead: 34px;   /* Title Body/Small */
  --fs-card-lg: 24px; --lh-card-lg: 36px; /* Card Header/Small */
  --fs-card: 24px;   --lh-card: 36px;   /* Card Header/Small */
  --fs-sub: 20px;    --lh-sub: 30px;    /* Card Subheader/Small */
  --fs-body: 14px;   --lh-body: 24px;   /* Body/Small */
  --ps-lead: 12px;                      /* Title Body paragraph spacing */
  --fs-intro: 32px;  --lh-intro: 40px;  /* `headers` title — Title Header/Small */
  --fs-skill-title: 24px; --lh-skill-title: 36px; /* Card Header/Small */
  --fs-cta: 24px;    --lh-cta: 36px;    /* closing CTA — Card Header/Small */
  --fw-cta: 500;
  --label-gap: 40px;    /* section label → its content (Figma: 40 / 56 / 48) */
  --step-gap: 24px;     /* step number → step text */
  --contact-btn-px: 18px;

  /* Case studies (390) */
  --split-gap: 24px;
  --aside-gap: 40px;
  --meta-gap: 40px;
  --panel-gap: 16px;
  --panel-row-gap: 4px;   /* icon sits above the text at 390 */
  --panel-icon-pad: 8px;
  --panel-icon: 16px;
  --fs-quote2: 32px; --lh-quote2: 40px;   /* Title Header/Small */
  --fs-stat2: 32px;  --lh-stat2: 40px;    /* Title Header/Small */
  --fs-stat-label: 20px; --lh-stat-label: 30px; /* Card Subheader/Small */
  --ps-quote: 20px;       /* Title Header/Small paragraph spacing */
  --process-gap: 40px;    /* heading over a card row (DSW "Process") */
  --cards-gap: 40px;      /* the same, where the 1200 frame tightens to 24 */

  /* Legacy type (pages not yet moved) */
  --fs-h1: var(--fs-hero);
  --lh-h1: var(--lh-hero);
  --ls-h1: 0px;
  --ls-lead: 0px;
  --fs-h2: 24px;
  --lh-h2: 32px;
  --fs-h3: 24px;
  --lh-h3: 32px;
  --fs-skill: 18px;
  --lh-skill: 24px;
  --skill-gap: 24px;
  --fs-step-num: 20px;
  --lh-step-num: 24px;
  --fs-quote: 24px;
  --lh-quote: 32px;
  --fs-stat: 48px;
  --lh-stat: 56px;
  --card-pad: 24px;

  --marquee-tile: 95px;
  --marquee-duration: 32s;

  /* Motion */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur: 220ms;
  /* Arrows rest pointing up-right and rotate to point right on hover.
     The glyph sits at 45deg, so a +45deg (clockwise) turn lands it horizontal. */
  --arrow-rotate: 45deg;
  --arrow-dur: 480ms;
  --arrow-ease: cubic-bezier(0.22, 1, 0.36, 1); /* ease-out quint */
}

/* DS "Colors" collection, Dark mode. Overlays flip from ink to white. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --c-bg: #0e0f10;
  --c-bg-hover: #0e0f10;
  --c-text: #ffffff;
  --c-text-2: rgba(255, 255, 255, 0.35);
  --c-text-3: rgba(255, 255, 255, 0.15);
  --c-link: rgba(255, 255, 255, 0.9);
  --c-line: rgba(255, 255, 255, 0.1);
  --c-overlay: rgba(255, 255, 255, 0.05);
  --c-overlay-hover: rgba(255, 255, 255, 0.15);
  --c-overlay-press: rgba(255, 255, 255, 0.1);
  --c-field: rgba(255, 255, 255, 0.05);
  --c-meta: rgba(255, 255, 255, 0.6);

  /* In Dark mode the DS turns icon-button glyphs, nav underlines and the
     mobile chevron accent green. */
  --c-btn-icon: #d7ff38;
  --c-btn-primary: rgba(255, 255, 255, 0.05);
  --c-btn-primary-hover: rgba(255, 255, 255, 0.15);
  --c-selector: #d7ff38;
  --c-caret: rgba(255, 255, 255, 0.25);
  --c-menu-caret: #d7ff38;
}

@media (min-width: 768px) {
  :root {
    --pad: 32px;
    --nav-pad-y: 24px;
    --nav-h: 88px;
    --hero-peek: 80px;

    --band-y: 56px;
    --band-gap: 56px;
    --rule-space: 56px;

    --grid-gap: 32px;
    --grid-row-gap: 56px;
    --card-text-pr: 32px;

    --footer-pad-y: 56px;
    --footer-gap: 56px;
    --footer-cta-gap: 56px;
    --cta-h: 48px;
    --cta-pad-x: 32px;
    --cta-icon: 22px;

    --hero-gap: 48px;
    --section-gap: 32px;
    --rule-gap: 32px;

    --fs-hero: 72px;   --lh-hero: 80px;   /* Title Hero/Base */
    --fs-page-title: calc(80px + (100vw - 768px) * 0.037); --lh-page-title: 1.1;
    --fs-title: 40px;  --lh-title: 48px;  /* Title Header/Base */
    --fs-lead: 22px;   --lh-lead: 38px;   /* Title Body/Base */
    --fs-card-lg: 32px; --lh-card-lg: 40px; /* Card Header/Base */
    --fs-sub: 24px;    --lh-sub: 34px;    /* Card Subheader/Base */
    --fs-body: 16px;   --lh-body: 30px;   /* Body/Base */
    --ps-lead: 14px;
    --fs-cta: 32px;    --lh-cta: 40px;    /* Title Header/Small */
    --fw-cta: 600;
    --label-gap: 56px;
    --step-gap: 32px;
    --contact-btn-px: 32px;

    --split-gap: 32px;
    --aside-gap: 56px;
    --meta-gap: 56px;
    --panel-gap: 28px;
    --panel-row-gap: 16px;
    --panel-icon: 24px;
    --fs-stat2: 40px;  --lh-stat2: 48px;    /* Title Header/Base */
    --fs-stat-label: 24px; --lh-stat-label: 34px; /* Card Subheader/Base */
    --process-gap: 56px;
    --cards-gap: 56px;

    --ls-h1: -0.12px;
    --fs-h3: 32px;
    --lh-h3: 40px;
    --fs-skill: 20px;
    --lh-skill: 24px;
    --skill-gap: 32px;
    --fs-quote: 40px;
    --lh-quote: 48px;
    --fs-stat: 64px;
    --lh-stat: 72px;
    --card-pad: 32px;

    --marquee-tile: 152px;
    --marquee-duration: 52s;
  }
}

@media (min-width: 1200px) {
  :root {
    --hero-peek: 96px;
    --fs-page-title: min(8vw, 125px); --lh-page-title: 1.083;
    --lead-max: 1064px;
    --pad: 64px;

    --band-y: 80px;
    --band-gap: 80px;
    --rule-space: 80px;

    --grid-gap: 48px;
    --grid-row-gap: 80px;
    --card-text-pr: 2px;

    --footer-gap: 80px;
    --footer-cta-gap: 48px;
    --cta-h: 56px;
    --cta-pad-x: 24px;
    --cta-icon: 24px;

    --hero-gap: 64px;
    --section-gap: 64px;
    --rule-gap: 48px;

    --fs-title: 48px;  --lh-title: 56px;  /* Title Header/Large */
    --fs-lead: 24px;   --lh-lead: 40px;   /* Title Body/Large */
    --ps-lead: 16px;
    --fs-intro: 48px;  --lh-intro: 56px;  /* Title Header/Large */
    --fs-skill-title: 32px; --lh-skill-title: 40px; /* Card Header/Base */
    --fs-cta: 40px;    --lh-cta: 48px;    /* Title Header/Base */
    --label-gap: 48px;
    --step-gap: 48px;
    --contact-btn-px: 24px;

    --split-gap: 48px;
    --aside-gap: 64px;
    --meta-gap: 48px;
    --panel-gap: 32px;
    --panel-row-gap: 24px;
    --panel-icon-pad: 16px;
    --fs-quote2: 40px; --lh-quote2: 48px;   /* Title Header/Base */
    --ps-quote: 24px;
    --process-gap: 48px;
    --cards-gap: 24px;
    --fs-stat2: 48px;  --lh-stat2: 56px;    /* Title Header/Large */

    --ls-h1: -0.18px;
    --fs-h3: 40px;
    --lh-h3: 48px;
    --fs-skill: 32px;
    --lh-skill: 40px;
    --fs-step-num: 32px;
    --lh-step-num: 40px;
    --fs-quote: 56px;
    --lh-quote: 64px;
    --fs-stat: 72px;
    --lh-stat: 80px;
    --card-pad: 48px;
    --skill-gap: 40px;

    --marquee-tile: 200px;
    --marquee-duration: 68s;
  }
}

/* ---------- Reset -------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Safari (iOS 26) tints its toolbars and the area around the page from the
     root element's own background, not the body's, so it's set here too. */
  background-color: var(--c-bg);
  /* Links and buttons draw their own pressed states instead of the browser's
     grey tap flash. */
  -webkit-tap-highlight-color: transparent;
}

body {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-text);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
  font-family: var(--f-sans);
  font-size: 16px;
  line-height: 24px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4,
p,
figure,
blockquote,
ul,
ol,
dl,
dd {
  margin: 0;
}

ul,
ol {
  padding: 0;
  list-style: none;
}

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

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

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

.skip-link {
  position: absolute;
  left: 8px;
  top: -100px;
  z-index: 100;
  padding: 12px 20px;
  background: var(--c-ink);
  color: var(--c-white);
  border-radius: 8px;
  transition: top var(--dur) var(--ease);
}

.skip-link:focus {
  top: 8px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- Layout primitives -------------------------------------------- */

.shell {
  width: 100%;
  max-width: calc(var(--page-max) + 2 * var(--pad));
  margin-inline: auto;
  padding-inline: var(--pad);
}

.section {
  width: 100%;
  padding-block: var(--pad);
}

.section__inner {
  display: flex;
  flex-direction: column;
  gap: var(--section-gap);
  align-items: flex-start;
}

.section--dark {
  background: var(--c-ink);
  color: var(--c-white);
}

/* A page band — the new layout's section. Figma "Header section" /
   ".Featured-work" / ".Other-work": 40/56/80 padding and rhythm. */
.band {
  width: 100%;
  padding-block: var(--band-y);
}

.band--flush-top {
  padding-top: 0;
}

.band--flush {
  padding-block: 0;
}

.band__inner {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--band-gap);
}

/* Divider row — a 1px rule with its eyebrow labels a full step below.
   Figma `.divider-row` (Divider-Row, Type=Divider). */
.section-label {
  width: 100%;
  border-top: 1px solid var(--c-line);
  padding-top: var(--rule-space);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  /* Figma spaces labels only by space-between (no column gap), which is what
     lets four of them share one row at 390. */
  gap: 18px 0;
}

.section--dark .section-label {
  border-top-color: rgba(255, 255, 255, 0.14);
}

/* Hatched ground behind a heading — the DS `.bg-pattern` (2175:238): 0.75px
   lines at 5% of the text colour, running at 65deg, 12.4847px apart along
   the x axis (11.315px apart square to the lines).

   It's drawn as one continuous repeating gradient rather than tiling the
   SVG, whose fractional tile size left seams on phone and tablet screens.
   The layer is one period wider than the box so the drift below can slide
   it with a transform (smooth on the GPU) and loop without a jump; the box
   clips it. */
.hatch {
  --hatch-step: 12.4847px;
  position: relative;
  isolation: isolate;
  overflow: clip;
}

.hatch::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: calc(-1 * var(--hatch-step));
  z-index: -1;
  background: repeating-linear-gradient(115deg,
    color-mix(in srgb, currentColor 5%, transparent) 0 0.75px,
    transparent 0.75px 11.315px);
  pointer-events: none;
}

/* ---------- Type --------------------------------------------------------- */

/* Eyebrow/Xtra Small */
.t-eyebrow {
  font-family: var(--f-sans);
  font-size: 12px;
  line-height: 22px;
  text-transform: uppercase;
  color: var(--c-text-3);
}

/* Colour emoji take the alpha of `color`, so a 25% eyebrow would fade the
   glyph too. Figma draws it at full strength. */
.emoji {
  color: var(--c-text);
}

.t-eyebrow--accent {
  color: var(--c-accent);
}

.t-meta {
  font-family: var(--f-sans);
  font-weight: 500;
  font-size: 12px;
  line-height: 22px;
  color: var(--c-text);
}

.section--dark .t-meta {
  color: var(--c-white);
}

/* Title Hero/Base — Title Header/Base at 390 */
.t-hero,
.t-display {
  font-family: var(--f-display);
  font-weight: 600;
  font-size: var(--fs-hero);
  line-height: var(--lh-hero);
  color: var(--c-text);
  overflow-wrap: break-word;
}

.t-hero {
  font-size: var(--fs-page-title);
  line-height: var(--lh-page-title);
}

/* Title Header — Small / Base / Large */
.t-title {
  font-family: var(--f-display);
  font-weight: 600;
  font-size: var(--fs-title);
  line-height: var(--lh-title);
  color: var(--c-text);
}

.t-h2 {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  color: var(--c-text);
}

.section--dark .t-h2 {
  color: var(--c-white);
}

/* Title Body — Small / Base / Large */
.t-lead {
  font-family: var(--f-sans);
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--c-text-2);
  max-width: var(--lead-max);
}

/* Code/Micro */
.t-mono {
  font-family: var(--f-mono);
  font-size: 10px;
  line-height: 16px;
  color: var(--c-text-3);
}

/* ---------- Icon --------------------------------------------------------- */

.icon {
  /* inline-flex so the box sizes correctly even outside a flex parent —
     an inline span ignores width/height and the SVG blows up to its default. */
  display: inline-flex;
  flex: none;
  width: 24px;
  height: 24px;
  color: var(--c-accent);
}

/* Lucide scales cleanly: the DS draws 20px and 16px glyphs from the same
   24-unit geometry, so only the box changes. */
.icon--20 {
  width: 20px;
  height: 20px;
}

.icon--16 {
  width: 16px;
  height: 16px;
}

.icon--ink {
  color: currentColor;
}

.icon svg {
  width: 100%;
  height: 100%;
  /* Lucide geometry: stroked, never filled. */
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------- Buttons ------------------------------------------------------ */

/* Text link — "Find out how ↗". Figma `.link`, type=primary. */
.btn-text {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  line-height: 30px;
  color: var(--c-link);
  cursor: pointer;
  transition: color var(--dur) var(--ease);
}

.btn-text:hover,
.btn-text:focus-visible {
  color: var(--c-text);
}

.btn-text .icon {
  transition: transform var(--dur) var(--ease);
}

.btn-text:hover .icon,
.btn-text:focus-visible .icon {
  transform: rotate(var(--arrow-rotate));
}

/* Row button — label left, arrow right */
.btn-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-width: 120px;
  width: 100%;
  font-size: 16px;
  line-height: 24px;
  color: var(--c-white);
}

.btn-row .icon {
  transition: transform var(--dur) var(--ease);
}

.btn-row:hover .icon,
.btn-row:focus-visible .icon {
  transform: rotate(var(--arrow-rotate));
}

/* Button — Figma `.button`. The DS moved from pills to a 4px corner. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 24px;
  border-radius: var(--radius);
  font-size: 16px;
  line-height: 24px;
  cursor: pointer;
  transition: filter var(--dur) var(--ease), background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.btn:hover,
.btn:focus-visible {
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.06)) drop-shadow(0 8px 12px rgba(0, 0, 0, 0.12));
}

.btn:active {
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.04));
}

.btn .icon {
  transition: transform var(--dur) var(--ease);
}

.btn:hover .icon,
.btn:focus-visible .icon {
  transform: rotate(var(--arrow-rotate));
}

/* A mirrored arrow (the "back" links) turns the opposite way, so it lands
   pointing straight left rather than straight up. */
.btn:hover .icon--flip,
.btn:focus-visible .icon--flip {
  transform: rotate(calc(var(--arrow-rotate) * -1));
}

/* Pressed: the label holds still and only the arrow steps away from it. The
   translate sits left of the rotate so it moves along the screen axis, not the
   glyph's rotated one. These follow the :hover rules because they carry equal
   specificity — source order is what makes them win. */
.btn:active .icon {
  transform: translateX(8px) rotate(var(--arrow-rotate));
}

.btn:active .icon--flip {
  transform: translateX(-8px) rotate(calc(var(--arrow-rotate) * -1));
}

/* Only arrows turn. Other button icons lift a hair on hover and settle
   when pressed, on the same curve as the arrows. */
.btn .icon--copy,
.btn .icon--mail {
  transition: transform var(--arrow-dur) var(--arrow-ease);
}

.btn:hover .icon--copy,
.btn:focus-visible .icon--copy,
.btn:hover .icon--mail,
.btn:focus-visible .icon--mail {
  transform: translate3d(0, -2px, 0);
}

.btn:active .icon--copy,
.btn:active .icon--mail {
  transform: translate3d(0, 1px, 0);
}

/* Secondary (accent) — Button/Background/Secondary. Text is ink at 75%,
   full ink on hover; the icon stays ink. */
.btn--green,
.btn--accent {
  background: var(--c-accent);
  color: rgba(14, 15, 16, 0.75);
}

.btn--green:hover,
.btn--green:focus-visible,
.btn--accent:hover,
.btn--accent:focus-visible {
  color: var(--c-ink);
}

.btn--green .icon,
.btn--accent .icon {
  color: var(--c-ink);
}

/* Primary — ink ground, white 80% text, accent icon */
.btn--primary {
  background: var(--c-btn-primary);
  color: var(--c-on-dark);
}

.btn--primary:hover,
.btn--primary:focus-visible {
  background: var(--c-btn-primary-hover);
  color: var(--c-white);
}

/* Tertiary — Button/Background/Tertiary: a 5% tint of the text colour. */
.btn--tertiary {
  background: var(--c-overlay);
  color: var(--c-link);
}

.btn--tertiary:hover,
.btn--tertiary:focus-visible {
  background: var(--c-overlay-hover);
  color: var(--c-text);
}

.btn--tertiary:active {
  background: var(--c-overlay-press);
}

.btn--tertiary .icon {
  color: var(--c-accent);
}

.btn--primary .icon {
  color: var(--c-accent);
}

.btn--secondary {
  background: var(--c-bg);
  color: var(--c-text);
  border: 1px solid var(--c-line);
}

.btn--secondary .icon {
  color: var(--c-accent);
}

.btn--secondary:hover,
.btn--secondary:focus-visible {
  background: var(--c-ink);
  border-color: var(--c-ink);
  color: var(--c-white);
}

/* Page-level call to action — 40/48/56 tall, full width until 768. */
.btn--cta {
  width: 100%;
  height: var(--cta-h);
  padding: 0 var(--cta-pad-x);
}

.btn--cta .icon {
  width: var(--cta-icon);
  height: var(--cta-icon);
}

@media (min-width: 768px) {
  .btn--cta {
    width: auto;
  }
}

.btn--block {
  width: 100%;
}

@media (min-width: 768px) {
  .btn--block {
    width: auto;
  }
}

/* Square tinted button — Figma `.button-icon` (tertiary): 40px, ink 5% ground,
   20px glyph. Used for the theme toggle and the mobile menu controls. */
.tool-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: var(--radius);
  color: var(--c-btn-icon);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.tool-btn--tint {
  background: var(--c-overlay);
  -webkit-backdrop-filter: blur(32px);
  backdrop-filter: blur(32px);
}

.tool-btn:hover,
.tool-btn:focus-visible {
  background: var(--c-overlay-hover);
}

.tool-btn:active {
  background: var(--c-overlay-press);
}

/* ---------- Theme toggle ------------------------------------------------- */
/* js/site.js marks the button with the glyph it shows. The moon rocks and
   the sun turns on hover, both dip when pressed, and the new glyph spins in
   when the theme changes. */
[data-theme-toggle] .icon {
  transition: transform var(--dur) var(--ease);
}

[data-theme-toggle][data-icon="moon"]:hover .icon,
[data-theme-toggle][data-icon="moon"]:focus-visible .icon {
  animation: moon-rock 0.7s var(--ease);
}

[data-theme-toggle][data-icon="sun"]:hover .icon,
[data-theme-toggle][data-icon="sun"]:focus-visible .icon {
  transform: rotate(45deg);
  transition-duration: 0.5s;
}

[data-theme-toggle]:active .icon {
  animation: none;
  transform: scale(0.82);
  transition-duration: 0.1s;
}

[data-theme-toggle].is-switching .icon {
  animation: theme-swap 0.45s var(--ease-out-expo, var(--ease));
}

@keyframes moon-rock {
  0%   { transform: none; }
  25%  { transform: rotate(-18deg); }
  55%  { transform: rotate(10deg); }
  80%  { transform: rotate(-4deg); }
  100% { transform: none; }
}

@keyframes theme-swap {
  from { transform: rotate(-90deg) scale(0.5); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* ---------- Main nav ----------------------------------------------------- */

/* Over imagery (js/site.js sets data-tone), the nav takes whichever of white
   or ink contrasts better with what's behind it. The frosted ground darkens or
   lightens a little so the labels keep passing over busy pictures. */
.nav[data-tone="light"] {
  --c-text: #ffffff;
  --c-link: rgba(255, 255, 255, 0.9);
  --c-caret: rgba(255, 255, 255, 0.7);
  --c-btn-icon: #d7ff38;
  --c-selector: #d7ff38;
  --c-overlay: rgba(14, 15, 16, 0.3);
  --c-overlay-hover: rgba(14, 15, 16, 0.45);
  --c-overlay-press: rgba(14, 15, 16, 0.4);
}

.nav[data-tone="dark"] {
  --c-text: #0e0f10;
  --c-link: rgba(14, 15, 16, 0.9);
  --c-caret: rgba(14, 15, 16, 0.7);
  --c-btn-icon: #0e0f10;
  --c-selector: #0e0f10;
  --c-overlay: rgba(255, 255, 255, 0.4);
  --c-overlay-hover: rgba(255, 255, 255, 0.55);
  --c-overlay-press: rgba(255, 255, 255, 0.5);
}
/* Figma `.main-nav`. The bar itself is transparent — only the link pill and
   the theme toggle carry the frosted ground, so content scrolls beneath. */

.nav {
  position: sticky;
  top: 0;
  z-index: 30;
  width: 100%;
  padding-block: var(--nav-pad-y);
}

.nav__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.nav__pill {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  border-radius: var(--radius);
}

/* The frosted ground lives on a pseudo-element, not the pill. An element with
   backdrop-filter becomes a backdrop root, and the dropdown nested inside it
   could then only blur the pill — not the page behind. */
.nav__pill::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--c-overlay);
  -webkit-backdrop-filter: blur(32px);
  backdrop-filter: blur(32px);
  transition: background-color var(--dur) var(--ease);
}

.nav__logo {
  display: flex;
  flex: none;
  width: 40px;
  height: 40px;
  color: var(--c-text);
  transition: color var(--dur) var(--ease);
}

.nav__logo svg {
  width: 40px;
  height: 40px;
  fill: currentColor;
  opacity: 0.8;
  transition: opacity var(--dur) var(--ease);
}

.nav__logo:hover svg,
.nav__logo:focus-visible svg {
  opacity: 1;
}

.nav__links {
  display: none;
  align-items: center;
  gap: 16px;
  padding-inline: 20px;
}

.nav__item {
  display: flex;
  align-items: center;
  gap: 4px;
}

.nav__link {
  position: relative;
  display: flex;
  align-items: center;
  height: 40px;
  font-size: 16px;
  line-height: 30px;
  color: var(--c-link);
  white-space: nowrap;
  transition: color var(--dur) var(--ease);
}

/* The `selector` bar — grows in from the left under the label. */
.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 0;
  background: var(--c-selector);
  transition: width var(--dur) var(--ease);
}

.nav__link:hover,
.nav__link:focus-visible,
.nav__link[aria-current="page"] {
  color: var(--c-text);
}

.nav__link:hover::after,
.nav__link:focus-visible::after,
.nav__link[aria-current="page"]::after,
.nav__item.is-open > .nav__link::after {
  width: 32px;
}

/* Chevron beside "Work" — its own button so the label can stay a link. */
.nav__caret {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 40px;
  color: var(--c-caret);
  transition: color var(--dur) var(--ease);
}

.nav__caret:hover,
.nav__caret:focus-visible,
.nav__item.is-open .nav__caret {
  color: var(--c-text);
}

.nav__caret .icon {
  transition: transform var(--dur) var(--ease);
}

.nav__item.is-open .nav__caret .icon {
  transform: rotate(180deg);
}

.nav__menu-btn {
  display: inline-flex;
}

@media (min-width: 768px) {
  .nav__links {
    display: flex;
  }
  .nav__menu-btn {
    display: none;
  }
}

/* Frosted panel shared by the projects dropdown and the mobile menu —
   Figma `.menu`: ink 5% over a heavy backdrop blur. */
.menu {
  border-radius: var(--radius);
  background: var(--c-overlay);
  -webkit-backdrop-filter: blur(64px);
  backdrop-filter: blur(64px);
  color: var(--c-text);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .menu {
    background: var(--c-bg-hover);
  }
}

.menu[hidden],
.menu__links[hidden] {
  display: none;
}

.menu__links {
  display: flex;
  flex-direction: column;
}

.menu__link {
  position: relative;
  display: flex;
  align-items: center;
  width: fit-content;
  height: 40px;
  font-size: 16px;
  line-height: 30px;
  color: var(--c-link);
  transition: color var(--dur) var(--ease), opacity 0.4s var(--arrow-ease);
}

/* Same selector bar as the main nav links: 32px, Button/Selector (ink in
   light, lime in dark), grown in from the left on hover, focus and the
   current page. Hovering the list also quiets the other projects. */
.menu__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 0;
  background: var(--c-selector);
  transition: width var(--dur) var(--ease);
}

/* Hover styles only where there is a real pointer: on touch, :hover sticks
   after a tap and would leave the other projects dimmed. */
@media (hover: hover) {
  .menu__links:hover .menu__link:not(:hover) {
    opacity: 0.35;
  }
  .menu__link:hover {
    color: var(--c-text);
  }
  .menu__link:hover::after {
    width: 32px;
  }
}

.menu__links:has(.menu__link:focus-visible) .menu__link:not(:focus-visible) {
  opacity: 0.35;
}

.menu__link:focus-visible,
.menu__link:active,
.menu__link[aria-current="page"] {
  color: var(--c-text);
}

.menu__link:focus-visible::after,
.menu__link:active::after,
.menu__link[aria-current="page"]::after {
  width: 32px;
}

.menu__foot {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.menu__social {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
}

/* Desktop / tablet projects dropdown — sits 8px under the pill, flush with
   its left edge. Positioned against .nav__pill. */
.dropdown {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  width: 503px;
  max-width: calc(100vw - 2 * var(--pad));
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding: 32px;
}

/* Mobile menu — drops over the top of the page */
.mobile-menu {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  max-height: 100dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 40px;
  /* The top padding matches the nav, so the logo and close button sit exactly
     where the nav's logo and theme toggle are. */
  padding-block: var(--nav-pad-y) 32px;
  border-radius: 0 0 var(--radius) var(--radius);
}

.mobile-menu__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: 18px;
}

.mobile-menu__body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding-inline: 24px;
}

.mobile-menu__primary {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Title Body/Large — 24/40 */
.mobile-menu__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  width: fit-content;
  height: 40px;
  font-size: 24px;
  line-height: 40px;
  color: var(--c-link);
  transition: color var(--dur) var(--ease);
}

.mobile-menu__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 0;
  background: var(--c-selector);
  transition: width var(--dur) var(--ease);
}

@media (hover: hover) {
  .mobile-menu__link:hover {
    color: var(--c-text);
  }
}

.mobile-menu__link:focus-visible,
.mobile-menu__link:active,
.mobile-menu__link[aria-current="page"],
.mobile-menu__link[aria-expanded="true"] {
  color: var(--c-text);
}

/* Pressing a link grows the selector bar in, as hover does on desktop. */
.mobile-menu__link:focus-visible::after,
.mobile-menu__link:active::after,
.mobile-menu__link[aria-current="page"]::after,
.mobile-menu__link[aria-expanded="true"]::after {
  width: 40px;
}

.mobile-menu__link .icon {
  color: var(--c-menu-caret);
  transition: transform var(--dur) var(--ease);
}

.mobile-menu__link[aria-expanded="true"] .icon {
  transform: rotate(90deg);
}

.mobile-menu .menu__foot {
  padding-inline: 32px;
}

/* The page behind stays put while the menu is open. --scrollbar-w (set by
   js/site.js) keeps the space a classic scrollbar took, so the nav and the
   menu's close button line up with the theme toggle and nothing shifts. */
.is-menu-open {
  overflow: hidden;
}

.is-menu-open body,
.is-menu-open .mobile-menu {
  padding-right: var(--scrollbar-w, 0px);
}

@media (min-width: 768px) {
  .mobile-menu {
    display: none !important;
  }
}

/* Social link — label with an accent arrow (Figma `.link`, type=ghost) */
.social-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 30px;
  font-size: 16px;
  line-height: 30px;
  color: var(--c-link);
  white-space: nowrap;
  transition: color var(--dur) var(--ease);
}

@media (hover: hover) {
  .social-link:hover {
    color: var(--c-text);
  }
  .social-link:hover .icon {
    transform: rotate(var(--arrow-rotate));
  }
}

.social-link:focus-visible,
.social-link:active {
  color: var(--c-text);
}

.social-link .icon {
  transition: transform var(--dur) var(--ease);
}

.social-link:focus-visible .icon,
.social-link:active .icon {
  transform: rotate(var(--arrow-rotate));
}

/* ---------- Page hero ---------------------------------------------------- */
/* Figma "Header section": eyebrow → title + lead → link row. */

.hero {
  overflow: clip;
  padding-block: var(--band-y);
}

.hero__inner {
  display: flex;
  flex-direction: column;
  gap: var(--band-gap);
  align-items: flex-start;
  /* The first screen is almost all hero: only --hero-peek of the next
     section shows below it. Nav, the hero's own top padding and the
     spacing before the next content come off a full screen. */
  min-height: calc(100svh - var(--nav-h) - 2 * var(--band-y) - var(--hero-peek));
}

/* When the next section opens with a ruled label (home, work), its content
   sits a rule-space lower; count that so the same amount still shows. */
.hero:has(+ .band > .band__inner > .section-label:first-child) .hero__inner {
  min-height: calc(100svh - var(--nav-h) - 2 * var(--band-y) - var(--rule-space) - var(--hero-peek));
}

/* Extra height opens above the last row (the label row), keeping it at
   the bottom of the screen. */
.hero__inner > :last-child {
  margin-top: auto;
}

.hero__content {
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 100%;
}

.hero__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 16px;
  width: 100%;
}

/* ---------- "What I do" stepper ----------------------------------------- */
/* Figma "What I do - stepping scroll through words". The resting state is the
   designed fade (1 → .1); js/site.js lights each line as it scrolls past. */

.stepper {
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 100%;
  font-family: var(--f-display);
  font-weight: 600;
  font-size: var(--fs-hero);
  line-height: var(--lh-hero);
  color: var(--c-text);
}

.stepper li {
  transition: opacity 400ms var(--ease);
}

.stepper li:nth-child(2) { opacity: 0.7; }
.stepper li:nth-child(3) { opacity: 0.6; }
.stepper li:nth-child(4) { opacity: 0.4; }
.stepper li:nth-child(5) { opacity: 0.2; }
.stepper li:nth-child(n + 6) { opacity: 0.1; }

/* Once JS is driving it, each line reads its own level. */
.stepper.is-live li {
  opacity: var(--o, 1);
}

/* Under motion.js the lines stay at full strength and the words carry the
   fade instead. */
.motion .stepper.m-scrub li {
  opacity: 1;
}

/* In the stepper, words not reached yet are fully hidden behind a blur and
   sharpen in as they light. */
.motion .stepper.m-scrub .s-word {
  display: inline-block;
  opacity: 0;
  filter: blur(10px);
  transition:
    opacity 0.5s var(--ease-out-expo),
    filter 0.5s var(--ease-out-expo);
}

.motion .stepper.m-scrub .s-word.is-lit {
  opacity: 1;
  filter: none;
}

/* ---------- Work cards --------------------------------------------------- */

/* Figma "Work" rows: two cards a row from 768, one below. */
.work-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  column-gap: var(--grid-gap);
  row-gap: var(--grid-row-gap);
  width: 100%;
}

@media (min-width: 768px) {
  .work-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Figma `.card-work` — image, then a 12px step to the title row. */
.work-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  cursor: pointer;
}

.work-card__media {
  position: relative;
  width: 100%;
  aspect-ratio: 1248 / 700;
  overflow: hidden;
  background: var(--c-overlay);
}

.work-card__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 400ms var(--ease);
}

.work-card:hover .work-card__media img,
.work-card:focus-visible .work-card__media img {
  transform: scale(1.02);
}

.work-card__foot {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
}

.work-card__text {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1 0 0;
  min-width: 0;
  padding-right: var(--card-text-pr);
}

/* Card Header/Small */
.work-card__title {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-card);
  line-height: var(--lh-card);
  color: var(--c-text);
  overflow-wrap: break-word;
}

/* The featured card steps up to Card Header/Base from 768. */
.work-card--feature .work-card__title {
  font-size: var(--fs-card-lg);
  line-height: var(--lh-card-lg);
}

/* Body/Xtra Small */
.work-card__company {
  font-family: var(--f-sans);
  font-size: 12px;
  line-height: 22px;
  color: var(--c-text-3);
}

.work-card .icon {
  /* Ink on the light page, lime in dark mode, so it reads on either. */
  color: var(--c-btn-icon);
  transition: transform var(--dur) var(--ease);
}

.work-card:hover .icon,
.work-card:focus-visible .icon {
  transform: rotate(var(--arrow-rotate));
}

/* ---------- Intro block ------------------------------------------------- */
/* Figma `headers` — eyebrow, heading, and a lead that may run to several
   paragraphs (spaced by the lead style's own paragraph spacing). */

.intro {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.intro__title {
  font-family: var(--f-display);
  font-weight: 600;
  font-size: var(--fs-intro);
  line-height: var(--lh-intro);
  color: var(--c-text);
}

.intro .t-lead {
  max-width: none;
}

.intro .t-lead + .t-lead {
  margin-top: calc(var(--ps-lead) - 8px);
}

/* ---------- Skill list --------------------------------------------------- */
/* Figma "Metadata Block": label, then skills split by full-width rules, all on
   the band rhythm. */

.skill-list {
  display: flex;
  flex-direction: column;
  gap: var(--band-gap);
  width: 100%;
}

.skill-list__rule {
  width: 100%;
  height: 0;
  margin: 0;
  border: 0;
  border-top: 1px solid var(--c-line);
}

.skill__title {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-skill-title);
  line-height: var(--lh-skill-title);
  color: var(--c-text);
}

.skill__detail {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text-2);
}

/* Numbered steps reuse the skill list: the number sits in its own column. */
.skill--step {
  display: flex;
  align-items: flex-start;
  gap: var(--step-gap);
}

.skill__num {
  flex: none;
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-skill-title);
  line-height: var(--lh-skill-title);
  color: var(--c-text);
  white-space: nowrap;
}

.skill__body {
  flex: 1 1 0;
  min-width: 0;
}

/* A section label with its content under it. */
.labelled {
  display: flex;
  flex-direction: column;
  gap: var(--label-gap);
  width: 100%;
}

/* Diagram — Figma `.image`, contained at 1072:603. The artwork is dark on
   transparent with no dark-mode version, so it gets a light ground there. */
.diagram {
  width: 100%;
  margin: 0;
}

.diagram img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1072 / 603;
  object-fit: contain;
}

/* Light and dark artwork; only the one for the current theme is shown (and,
   being lazy, only that one is fetched). */
.diagram__dark {
  display: none !important;
}

:root[data-theme="dark"] .diagram__light {
  display: none !important;
}

:root[data-theme="dark"] .diagram__dark {
  display: block !important;
}

/* ---------- Contact ------------------------------------------------------ */

/* The contact page ends with a longer run-out (Figma: 160 at every size). */
.band--tail {
  padding-bottom: 160px;
}

.contact-group {
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 100%;
}

.contact-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
  width: 100%;
}

/* Card Header/Base at every breakpoint. The address is wider than the 390
   column, so it may break after the @ (Figma lets it overflow the frame). */
.contact-email {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: 32px;
  line-height: 40px;
  color: var(--c-text);
  overflow-wrap: break-word;
}

.contact-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 8px;
}

.contact-actions .btn {
  height: 56px;
  padding: 0 var(--contact-btn-px);
}

.social-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 24px;
}

.social-row .social-link {
  color: var(--c-text);
}

@media (min-width: 1200px) {
  .contact-row {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

/* =============================================================================
   Case studies (Figma "Work-*" frames)
   ========================================================================== */

/* Sections stack on the band rhythm; each one is itself a band-gap column. */
.cs-flow {
  display: flex;
  flex-direction: column;
  gap: var(--band-gap);
  padding-bottom: var(--band-y);
}

.cs-section {
  display: flex;
  flex-direction: column;
  gap: var(--band-gap);
}

.cs-stack {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.cs-stack--24 { gap: 24px; }
.cs-stack--18 { gap: 18px; }
.cs-stack--band { gap: var(--band-gap); }
.cs-stack--process { gap: var(--process-gap); }
.cs-stack--cards { gap: var(--cards-gap); }

/* The case-study hero also holds a full-bleed figure, a band-gap below. */
.cs-hero {
  display: flex;
  flex-direction: column;
  gap: var(--band-gap);
}

/* Case-study leads run wider than the page heroes' below 1200 (888 vs
   768); from 1200 every lead shares the 1064 cap. */
.cs-hero .t-lead {
  max-width: max(888px, var(--lead-max));
}

/* Some case studies keep the 768 lead size (Title Body/Base) at 1200. */
@media (min-width: 1200px) {
  .cs-hero .t-lead--base {
    font-size: 22px;
    line-height: 38px;
  }
}

/* Meta row under the hero rule — company / role / industry / website. */
.cs-meta {
  width: 100%;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 18px var(--meta-gap);
}

.cs-meta__item {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.cs-meta__value {
  font-size: 16px;
  line-height: 30px;
  color: var(--c-text);
}

.cs-meta__value.btn-text {
  color: var(--c-link);
}

/* Full-width figure with an optional right-aligned mono caption. */
.cs-figure {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  margin: 0;
}

.cs-figure img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--c-overlay);
}

.cs-figure figcaption {
  padding-inline: 4px;
  font-family: var(--f-mono);
  font-size: 10px;
  line-height: 16px;
  text-align: right;
  color: var(--c-text-3);
}

/* The hero figure runs edge to edge of the 1200 frame; below that its
   caption keeps to the page gutter. */
.cs-figure--bleed {
  max-width: var(--page-max);
  margin-inline: auto;
}

.cs-figure--bleed figcaption {
  padding-inline: var(--pad);
}

@media (min-width: 1200px) {
  .cs-figure--bleed figcaption {
    padding-inline: 4px;
  }
}

/* Section heading — Figma `headers` at card scale: eyebrow, Card Header,
   Body paragraphs spaced by the Body paragraph spacing (12). */
.cs-sub {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  min-width: 0;
}

.cs-sub__body {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.cs-sub .skill__title + .cs-sub__body {
  margin-top: -8px;
}

/* Section headings that stay at Card Header/Small at every size. */
.cs-sub .skill__title--sm {
  font-size: 24px;
  line-height: 36px;
}

/* Pull quote — Figma `.quote`: Title Header paragraphs, then an optional
   Body Strong/Small attribution 16px below. */
.cs-quote {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin: 0;
}

.cs-quote__body {
  display: flex;
  flex-direction: column;
  gap: var(--ps-quote);
  margin: 0;
  font-family: var(--f-display);
  font-weight: 600;
  font-size: var(--fs-quote2);
  line-height: var(--lh-quote2);
  color: var(--c-text);
}

.cs-quote__attr {
  font-family: var(--f-sans);
  font-weight: 500;
  font-size: 14px;
  line-height: 24px;
  color: var(--c-text-2);
}

/* Info cards — Figma `.card-info`. They wrap at a 256px minimum, so three
   share a row at 1200, two at 768 and one at 390; a lone card fills its row. */
.cs-cards {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 24px;
  width: 100%;
}

.cs-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 256px;
  min-width: 256px;
  padding: var(--card-pad);
  background: var(--c-bg);
  /* Figma strokes sit inside the box, so they must not add to its size. */
  box-shadow: inset 0 0 0 1px var(--c-line);
}

.cs-card .skill__detail {
  color: var(--c-text-2);
}

/* The goal card stays ink in both themes (Gray/1000) with accent text. */
.cs-card--dark {
  background: var(--c-ink);
}

.cs-card--dark .skill__title,
.cs-card--dark .skill__detail {
  color: var(--c-accent);
}

/* A two-up row that never wraps: at 390 the cards keep their 256px minimum
   and the row scrolls sideways, as the Figma frame overflows. */
.cs-cards--row {
  flex-wrap: nowrap;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.cs-cards--row::-webkit-scrollbar {
  display: none;
}

.cs-cards--row .cs-card {
  flex: 1 0 256px;
  scroll-snap-align: start;
}

/* A two-up row that stacks full width below 1200 instead of scrolling. */
.cs-cards--stack {
  flex-direction: column;
  flex-wrap: nowrap;
}

.cs-cards--stack .cs-card {
  flex: none;
  min-width: 0;
}

@media (min-width: 1200px) {
  .cs-cards--stack {
    flex-direction: row;
  }
  .cs-cards--stack .cs-card {
    flex: 1 1 0;
  }
}

/* Text beside an image — Figma `.image-split`. Stacks text-first below 1200. */
.cs-split {
  display: flex;
  flex-direction: column;
  gap: var(--split-gap);
  width: 100%;
}

.cs-split > * {
  flex: 1 1 0;
  min-width: 0;
}

@media (min-width: 1200px) {
  .cs-split {
    flex-direction: row;
    align-items: center;
  }
  .cs-split--image-first > .cs-figure {
    order: -1;
  }
}

.cs-rule {
  width: 100%;
  height: 0;
  margin: 0;
  border: 0;
  border-top: 1px solid var(--c-line);
}

/* Panel — Figma `.card-panel`: bordered rows split by rules. */
.cs-panel {
  display: flex;
  flex-direction: column;
  gap: var(--panel-gap);
  width: 100%;
  padding: var(--card-pad);
  background: var(--c-bg);
  /* Figma strokes sit inside the box, so they must not add to its size. */
  box-shadow: inset 0 0 0 1px var(--c-line);
}

/* Icon above the text at 390, beside it from 768. */
.cs-panel__row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--panel-row-gap);
}

@media (min-width: 768px) {
  .cs-panel__row {
    flex-direction: row;
    align-items: center;
  }
}

.cs-panel__text {
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  min-width: 0;
}

.cs-panel__eyebrow + .cs-panel__row,
.cs-panel__head {
  margin-top: 0;
}

.cs-panel__cell {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Card Subheader/Base at every size */
.cs-panel__subhead {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: 24px;
  line-height: 34px;
  color: var(--c-text);
}

.cs-panel__icon {
  display: inline-flex;
  flex: none;
  padding: var(--panel-icon-pad);
  border-radius: 30px;
  background: #262728; /* Gray/900, both themes */
  color: var(--c-accent);
}

.cs-panel__icon .icon {
  width: var(--panel-icon);
  height: var(--panel-icon);
}

.cs-panel__text {
  width: 100%;
}

/* Heading beside a panel — "Stress testing". Figma splits the 1072 row
   455 : 553 at 1200; it stacks below that. */
.cs-aside {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--aside-gap);
  width: 100%;
  align-items: start;
}

@media (min-width: 1200px) {
  .cs-aside {
    grid-template-columns: minmax(256px, 455fr) minmax(0, 553fr);
  }
}

/* Stat cards — three across at 1200, stacked below. */
.cs-stats {
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 100%;
}

.cs-stat {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1 1 0;
  min-width: 0;
  padding: var(--card-pad);
  background: var(--c-bg);
  /* Figma strokes sit inside the box, so they must not add to its size. */
  box-shadow: inset 0 0 0 1px var(--c-line);
}

.cs-stat__value {
  font-family: var(--f-display);
  font-weight: 600;
  font-size: var(--fs-stat2);
  line-height: var(--lh-stat2);
  color: var(--c-text);
}

.cs-stat__label {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-stat-label);
  line-height: var(--lh-stat-label);
  color: var(--c-text);
}

/* Label and its supporting line sit flush, 8px under the value. */
.cs-stat__text {
  display: flex;
  flex-direction: column;
}

@media (min-width: 1200px) {
  .cs-stats {
    flex-direction: row;
    flex-wrap: wrap;
  }
}

/* Prev / next — Figma `.actions`: stacked full width at 390 (8px apart),
   pushed to either end from 768. */
.cs-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

@media (min-width: 768px) {
  .cs-actions {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

/* ---------- Closing CTA -------------------------------------------------- */
/* Figma `.actions` at the foot of a page. */

.cta-close {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  width: 100%;
}

.cta-close__title {
  font-family: var(--f-display);
  font-weight: var(--fw-cta);
  font-size: var(--fs-cta);
  line-height: var(--lh-cta);
  color: var(--c-text);
}

/* ---------- Footer ------------------------------------------------------- */
/* Figma `.footer`. Dark in both themes (Background/Default/Tertiary Hover). */

.footer {
  width: 100%;
  background: var(--c-ink);
  color: var(--c-white);
  border-top: 1px solid var(--c-line);
  padding-block: var(--footer-pad-y);
}

.footer__inner {
  display: flex;
  flex-direction: column;
  gap: var(--footer-gap);
}

.footer__top {
  display: flex;
  flex-direction: column;
  gap: var(--footer-gap);
}

.footer__cta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--footer-cta-gap);
  min-width: 256px;
}

.footer__cta-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
}

/* Card Header — Small at 390, Base above */
.footer__title {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-card-lg);
  line-height: var(--lh-card-lg);
  color: var(--c-white);
}


.footer__links {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 40px;
}

.footer__col {
  display: flex;
  flex-direction: column;
  gap: 16px;
  flex: 1 0 0;
  min-width: 120px;
}

.footer__link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 30px;
  font-size: 16px;
  line-height: 30px;
  color: var(--c-on-dark);
  transition: color var(--dur) var(--ease);
}

.footer__link:hover,
.footer__link:focus-visible {
  color: var(--c-white);
}

.footer__link .icon {
  transition: transform var(--dur) var(--ease);
}

.footer__link:hover .icon,
.footer__link:focus-visible .icon {
  transform: rotate(var(--arrow-rotate));
}

/* Bottom row: legal and the credit, with a quiet llms.txt link for AI agents
   always in the bottom-right corner. On phones credit and legal stack; from
   tablet up all three share one line. */
.footer__bottom {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
}

/* Stacked, the credit sits above the copyright. */
.footer__meta {
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
}

@media (min-width: 768px) {
  .footer__bottom {
    align-items: baseline;
  }
  .footer__meta {
    display: contents;
  }
}

.footer__heart {
  display: inline-block;
  width: 10px;
  height: 10px;
  vertical-align: -1px;
  fill: var(--c-accent);
}

.footer__agents {
  font-family: var(--f-mono);
  font-size: 10px;
  line-height: 16px;
  color: var(--c-on-dark-3);
  transition: color var(--dur) var(--ease);
}

.footer__agents:hover,
.footer__agents:focus-visible {
  color: var(--c-on-dark);
}

.footer__legal {
  font-family: var(--f-mono);
  font-size: 10px;
  line-height: 16px;
  color: var(--c-on-dark-3);
}

@media (min-width: 768px) {
  .footer__cta-head {
    gap: 8px;
  }
  .footer__links {
    column-gap: 56px;
  }
}

@media (min-width: 1200px) {
  .footer__top {
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
  }
  .footer__cta {
    flex: 1 0 0;
    max-width: 456px;
  }
  .footer__links {
    flex: 1 0 0;
    justify-content: flex-end;
    max-width: 400px;
    column-gap: 80px;
  }
}

/* ---------- Generic stacks / dividers ------------------------------------ */

.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--section-gap);
  width: 100%;
}

.stack--tight {
  gap: 24px;
}

.stack--rule {
  gap: var(--rule-gap);
}

.divider {
  width: 100%;
  height: 1px;
  border: 0;
  margin: 0;
  background: #d9d9d9;
  flex: none;
}

/* Sections whose vertical rhythm follows the section gap rather than the
   page gutter (contact, about, process). */
.section--rhythm {
  padding-block: var(--section-gap);
}

/* ---------- Contact list rows -------------------------------------------- */

.btn-row--ink {
  color: var(--c-ink);
}

.t-lead--wide {
  max-width: none;
}

/* ---------- About / process shared blocks --------------------------------- */

.t-h3 {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  color: var(--c-ink);
  width: 100%;
}

.t-body {
  font-family: var(--f-sans);
  overflow-wrap: break-word;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: -0.05px;
  color: var(--c-body);
}

.t-body + .t-body {
  margin-top: 16px;
}

/* Eyebrow rule with several evenly-spread labels (01 / About me — Columbus — Ohio) */
.label-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
}

.label-row > * {
  white-space: nowrap;
}

/* "What I do" style block: small label, then a statement + body copy */
.note-block {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
  min-width: 256px;
  width: 100%;
}

.note-block__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
}

/* ---------- Skill rows --------------------------------------------------- */

.skills {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--skill-gap);
  width: 100%;
}

.skill-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  row-gap: 8px;
  column-gap: 40px;
  padding-bottom: var(--skill-gap);
  border-bottom: 1px solid var(--c-line);
  width: 100%;
}

.skill-row:last-child {
  border-bottom: 0;
}

.skill-row__title {
  flex: 1 0 0;
  min-width: 256px;
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-skill);
  line-height: var(--lh-skill);
  color: var(--c-ink);
}

.skill-row__detail {
  flex: 1 0 0;
  min-width: 256px;
  font-family: var(--f-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: -0.05px;
  color: var(--c-body);
}

@media (min-width: 768px) {
  .skill-row {
    row-gap: var(--skill-gap);
    column-gap: var(--skill-gap);
  }
}

/* ---------- End-of-page CTA ---------------------------------------------- */

.cta-block {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
  min-width: 256px;
  width: 100%;
}

/* ---------- Numbered process steps --------------------------------------- */

.steps {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--skill-gap);
  width: 100%;
}

.step {
  display: flex;
  align-items: flex-start;
  gap: var(--skill-gap);
  padding-bottom: var(--skill-gap);
  border-bottom: 1px solid var(--c-line);
  width: 100%;
}

.step:last-child {
  border-bottom: 0;
}

.step__num {
  flex: none;
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-step-num);
  line-height: var(--lh-step-num);
  color: var(--c-ink);
}

.step__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
}

.step__title {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-skill);
  line-height: var(--lh-skill);
  color: var(--c-ink);
}

.step__text {
  font-family: var(--f-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: -0.05px;
  color: var(--c-body);
}

/* ---------- Inline media -------------------------------------------------- */

.media-img {
  width: 100%;
  height: auto;
}

/* =============================================================================
   Case study (work detail)
   ========================================================================== */

/* Sub-section heading — sits between .t-h3 and body copy */
.t-h4 {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-skill);
  line-height: var(--lh-skill);
  color: var(--c-ink);
  width: 100%;
}

@media (min-width: 1200px) {
  .t-h4 {
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
  }
}

/* Vertical rhythm for the whole case body, so full-bleed figures and padded
   text blocks share one gap. */
.case-flow {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--section-gap);
  padding-block: var(--section-gap);
  width: 100%;
}

/* ---------- Project meta strip ------------------------------------------- */

.case-meta {
  width: 100%;
  border-top: 1.5px solid var(--c-line);
  padding-top: var(--rule-gap);
}

.case-meta__list {
  display: flex;
  flex-wrap: wrap;
  row-gap: 24px;
  column-gap: var(--meta-gap);
}

.meta {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.meta__value {
  font-family: var(--f-sans);
  font-weight: 500;
  font-size: 16px;
  line-height: 24px;
  letter-spacing: -0.04px;
  color: var(--c-ink);
}

/* ---------- Full-bleed figures -------------------------------------------- */

.figure-full {
  margin: 0;
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
}

.figure-full img {
  width: 100%;
  height: auto;
}

/* ---------- Cards --------------------------------------------------------- */

.cards {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 24px;
  width: 100%;
}

.card {
  flex: 1 1 0;
  min-width: 256px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: var(--card-pad);
  background: var(--c-white);
  border: 1px solid var(--c-line);
}

.card__title {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  color: var(--c-ink);
}

.card__text {
  font-family: var(--f-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: -0.05px;
  color: var(--c-body);
}

.card--goal {
  background: var(--c-ink);
  border-color: var(--c-ink);
}

.card--goal .card__title,
.card--goal .card__text {
  color: var(--c-accent);
}

/* ---------- Two-up text / image splits ------------------------------------ */

.split {
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  gap: var(--section-gap);
  width: 100%;
}

.split img {
  width: 100%;
  height: auto;
}

@media (min-width: 768px) {
  .split {
    grid-template-columns: 1fr 1fr;
  }
  .split--reverse > :first-child {
    order: 2;
  }
}

/* ---------- Bordered panel (validation output, virality) ------------------ */

.panel {
  display: flex;
  flex-direction: column;
  gap: var(--card-pad);
  width: 100%;
  padding: var(--card-pad);
  border: 1.5px solid var(--c-line);
}

.panel__row {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.panel__row + .panel__row {
  border-top: 1.5px solid var(--c-line);
  padding-top: var(--card-pad);
}

.panel__stat {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-skill);
  line-height: var(--lh-skill);
  color: var(--c-ink);
}

/* ---------- Pull quote ---------------------------------------------------- */

.quote {
  margin: 0;
  width: 100%;
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-quote);
  line-height: var(--lh-quote);
  color: var(--c-ink);
}

/* ---------- Stat cards ---------------------------------------------------- */

.stat-cards {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 24px;
  width: 100%;
}

.stat-card {
  flex: 1 1 0;
  min-width: min(240px, 100%);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  padding: var(--card-pad);
  background: var(--c-white);
  border: 1px solid var(--c-line);
}

.stat-card__value {
  font-family: var(--f-display);
  font-weight: 700;
  font-size: var(--fs-stat);
  line-height: var(--lh-stat);
  color: var(--c-ink);
}

.stat-card__label {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: var(--fs-skill);
  line-height: var(--lh-skill);
  color: var(--c-ink);
}

.stat-card__text {
  font-family: var(--f-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: -0.05px;
  color: var(--c-body);
}

/* ---------- Virality list -------------------------------------------------- */

.virality {
  display: flex;
  align-items: flex-start;
  gap: 24px;
  width: 100%;
}

.virality__icon {
  flex: none;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  border: 1px solid var(--c-line);
  display: grid;
  place-items: center;
  font-size: 24px;
  line-height: 1;
}

.virality__body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  /* The deep-link example is one unbroken string — let it wrap on narrow screens. */
  overflow-wrap: anywhere;
}

/* ---------- Prev / next actions -------------------------------------------- */

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
}

.actions .btn {
  flex: 1 1 100%;
}

@media (min-width: 768px) {
  .actions .btn {
    flex: 0 0 auto;
  }
}

.icon--flip svg {
  transform: scaleX(-1);
}

.panel__head {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

/* Impact rows stay stacked until the desktop breakpoint. */
.skills--stack .skill-row {
  flex-direction: column;
  row-gap: 8px;
}

@media (min-width: 1200px) {
  .skills--stack .skill-row {
    flex-direction: row;
  }
}

/* ---------- Logo marquee -------------------------------------------------- */

/* Figma `.marquee` — a hatched strip of monochrome logos. */
.marquee {
  display: grid;
  width: 100%;
  overflow: hidden;
  /* Width of the soft zone at each side. */
  --marquee-edge: calc(var(--marquee-tile) * 0.9);
}

/* Both views share one cell. The masks sit on the views, which don't move,
   so the fade stays put while the logos travel through it; the hatch is on
   .marquee itself and is never masked. */
.marquee__view {
  grid-area: 1 / 1;
  min-width: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0,
    transparent calc(var(--marquee-edge) * 0.3),
    #000 var(--marquee-edge),
    #000 calc(100% - var(--marquee-edge)),
    transparent calc(100% - var(--marquee-edge) * 0.3),
    transparent 100%);
  mask-image: linear-gradient(90deg,
    transparent 0,
    transparent calc(var(--marquee-edge) * 0.3),
    #000 var(--marquee-edge),
    #000 calc(100% - var(--marquee-edge)),
    transparent calc(100% - var(--marquee-edge) * 0.3),
    transparent 100%);
}

/* The blurred copy only shows inside the soft zones, peaking partway in and
   fading to zero at the very edge. */
.marquee__view--blur {
  filter: blur(8px);
  pointer-events: none;
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0,
    #000 calc(var(--marquee-edge) * 0.4),
    #000 calc(var(--marquee-edge) * 0.55),
    transparent var(--marquee-edge),
    transparent calc(100% - var(--marquee-edge)),
    #000 calc(100% - var(--marquee-edge) * 0.55),
    #000 calc(100% - var(--marquee-edge) * 0.4),
    transparent 100%);
  mask-image: linear-gradient(90deg,
    transparent 0,
    #000 calc(var(--marquee-edge) * 0.4),
    #000 calc(var(--marquee-edge) * 0.55),
    transparent var(--marquee-edge),
    transparent calc(100% - var(--marquee-edge)),
    #000 calc(100% - var(--marquee-edge) * 0.55),
    #000 calc(100% - var(--marquee-edge) * 0.4),
    transparent 100%);
}

/* Re-blurring a moving strip every frame is heavy on phones and tablets and
   made the logos flicker; there the plain edge fade does the job alone. */
@media not ((hover: hover) and (pointer: fine)) {
  .marquee__view--blur {
    display: none;
  }
}

/* The track rides on its own GPU layer: in Safari, masked logos inside a
   moving layer that gets re-rasterised flash off for a frame. */
.marquee__track {
  display: flex;
  width: max-content;
  align-items: center;
  will-change: transform;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  animation: marquee-scroll var(--marquee-duration) linear infinite;
}

/* The track holds two identical sets, so shifting it exactly half a track
   width lands back on the starting frame. */
@keyframes marquee-scroll {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-50%, 0, 0);
  }
}

@media (hover: hover) and (pointer: fine) {
  .marquee:hover .marquee__track {
    animation-play-state: paused;
  }
}

.marquee:focus-within .marquee__track {
  animation-play-state: paused;
}

.marquee__item {
  position: relative;
  flex: none;
  width: var(--marquee-tile);
  height: var(--marquee-tile);
}

/* Hover: the grey mark gives way to the full-colour one (DS .marquee__item,
   Color=Full) and the tool's name rises in underneath, in Roboto Mono. */
.marquee__logo,
.marquee__color {
  transition:
    opacity 0.35s var(--ease),
    transform var(--arrow-dur) var(--arrow-ease);
}

.marquee__color {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
}

.marquee__color--mono {
  background-color: var(--c-text);
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.marquee__name {
  position: absolute;
  left: 4px;
  right: 4px;
  bottom: 12%;
  font-family: var(--f-mono);
  font-size: 12px;
  line-height: 16px;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--c-text-2);
  opacity: 0;
  transform: translate3d(0, 6px, 0);
  transition:
    opacity 0.35s var(--ease),
    transform var(--arrow-dur) var(--arrow-ease);
  pointer-events: none;
}

@media (max-width: 767px) {
  .marquee__name {
    bottom: 6%;
    font-size: 10px;
  }
}

/* Hover only where there's a pointer: on touch, a tap left the colour logo
   and the pause stuck on. */
@media (hover: hover) and (pointer: fine) {
  .marquee__item:hover .marquee__logo {
    opacity: 0;
    transform: scale(1.06) translate3d(0, -6%, 0);
  }

  .marquee__item:hover .marquee__color {
    opacity: 1;
    transform: scale(1.06) translate3d(0, -6%, 0);
  }

  .marquee__item:hover .marquee__name {
    opacity: 1;
    transform: none;
  }
}

/* Each logo SVG is ink at 40% on transparent, used as a mask so the mark
   takes the text colour (and its 40%) in either theme. The image URL is set
   inline, where it resolves against the page. */
.marquee__logo {
  display: block;
  width: 100%;
  height: 100%;
  background-color: var(--c-text);
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

@media (prefers-reduced-motion: reduce) {
  .marquee__view {
    overflow-x: auto;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .marquee__view--blur {
    display: none;
  }
  .marquee__track {
    animation: none;
  }
}

/* Quote attribution (case studies that cite a source). */
.quote p {
  margin: 0;
}

.quote__attr {
  display: block;
  margin-top: 24px;
  font-family: var(--f-sans);
  font-weight: 500;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-body);
}

/* External link inside the project meta strip. */
.meta__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: fit-content;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: rgba(17, 17, 17, 0.25);
  transition: text-decoration-color var(--dur) var(--ease);
}

.meta__link:hover,
.meta__link:focus-visible {
  text-decoration-color: var(--c-ink);
}

.meta__link .icon {
  width: 20px;
  height: 20px;
  transition: transform var(--dur) var(--ease);
}

.meta__link:hover .icon,
.meta__link:focus-visible .icon {
  transform: rotate(var(--arrow-rotate));
}

/* ---------- Icon button ---------------------------------------------------
   Figma component `.icon-button` (28:53624). Square, 8px inset around a 24px
   glyph. The arrow swaps from up-right to straight-right on hover — two
   different paths in the source, not a rotation.
   -------------------------------------------------------------------------- */

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  border: 0;
  cursor: pointer;
  transition: filter var(--dur) var(--ease), background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.icon-btn--primary {
  background: var(--c-ink);
}

.icon-btn--secondary {
  background: var(--c-white);
  border: 1px solid var(--c-line);
}

.icon-btn--secondary:hover,
.icon-btn--secondary:focus-visible {
  background: var(--c-ink);
  border-color: var(--c-ink);
}

.icon-btn:hover,
.icon-btn:focus-visible {
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.06)) drop-shadow(0 8px 12px rgba(0, 0, 0, 0.12));
}

.icon-btn:active {
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.04));
}

/* One glyph, rotated. The Figma hover variant is the same arrow pointing
   straight right; the resting variant points up-right. */
.icon-btn__glyphs {
  display: block;
  width: 24px;
  height: 24px;
  color: var(--c-accent);
}

.icon-btn__glyphs svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform var(--dur) var(--ease);
}

.icon-btn:hover .icon-btn__glyphs svg,
.icon-btn:focus-visible .icon-btn__glyphs svg {
  transform: rotate(var(--arrow-rotate));
}

/* Inline video — same footprint as .media-img, with an ink ground so the
   letterbox matches the poster while it loads. */
.media-video {
  display: block;
  width: 100%;
  height: auto;
  background: var(--c-ink);
}

/* =============================================================================
   Video player
   Native controls are replaced by JS (js/player.js) — the markup ships with
   `controls` so anyone without JS still gets a working player.
   ========================================================================== */

.player {
  position: relative;
  width: 100%;
  background: #262728; /* Gray/900, both themes */
  border: 1px solid var(--c-line);
  border-radius: var(--radius);
  overflow: hidden;
}

.player__video {
  display: block;
  width: 100%;
  height: auto;
  /* The source isn't exactly 16:9; the frame in Figma is. */
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: #262728;
}

/* Only applied once JS has taken over the controls. */
.player--custom .player__video {
  cursor: pointer;
}

/* ---------- Control bar --------------------------------------------------- */
/* Figma `.player__bar`: always on, 48 tall, an ink scrim fading up from the
   bottom edge with the DS shadow pair; lime controls, white mono time. */

.player__bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: none;
  align-items: center;
  gap: 16px;
  padding: 12px 14px;
  background: linear-gradient(to bottom, rgba(14, 15, 16, 0), #0e0f10);
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.06), 0 8px 12px rgba(0, 0, 0, 0.12);
}

.player--custom .player__bar {
  display: flex;
}

.player__btn {
  display: grid;
  place-items: center;
  flex: none;
  width: 24px;
  height: 24px;
  color: var(--c-accent);
  transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.player__btn:hover,
.player__btn:focus-visible {
  color: var(--c-white);
}

.player__btn:active {
  transform: scale(0.92);
}

.player__btn svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Code/Small */
.player__time {
  flex: none;
  font-family: var(--f-mono);
  font-size: 14px;
  line-height: 24px;
  color: var(--c-white);
  font-variant-numeric: tabular-nums;
}

/* ---------- Scrubber ------------------------------------------------------ */

.player__scrub {
  flex: 1 1 auto;
  min-width: 0;
  height: 24px;
  margin: 0;
  padding: 0;
  background: none;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}

.player__scrub::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--c-accent) 0%,
    var(--c-accent) var(--played, 0%),
    rgba(255, 255, 255, 0.1) var(--played, 0%),
    rgba(255, 255, 255, 0.1) 100%
  );
}

.player__scrub::-moz-range-track {
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
}

.player__scrub::-moz-range-progress {
  height: 4px;
  border-radius: 999px;
  background: var(--c-accent);
}

.player__scrub::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  margin-top: -4px;
  border: 0;
  border-radius: 50%;
  background: var(--c-accent);
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.06), 0 8px 12px rgba(0, 0, 0, 0.12);
  transition: transform var(--dur) var(--ease);
}

.player__scrub::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: 0;
  border-radius: 50%;
  background: var(--c-accent);
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.06), 0 8px 12px rgba(0, 0, 0, 0.12);
}

.player__scrub:hover::-webkit-slider-thumb,
.player__scrub:focus-visible::-webkit-slider-thumb {
  transform: scale(1.25);
}

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

/* Custom properties do not inherit into media fallback content, so a .t-body
   inside <video> would resolve to line-height: normal on a browser that shows
   the fallback. Literal values keep it on the body rules. */
.player__video .t-body {
  font-size: 16px;
  line-height: 24px;
}

/* ==========================================================================
   Password gate — js/locked.js
   ========================================================================== */

/* Below the hero until unlocked: a blurred stand-in for the content with a
   prompt over it, a screen tall so the scrim has something to open on. */
.cs-gate {
  position: relative;
  min-height: 100svh;
  padding-bottom: var(--band-y);
}

.cs-ghost {
  display: flex;
  flex-direction: column;
  gap: 24px;
  filter: blur(10px);
  opacity: 0.6;
  pointer-events: none;
  user-select: none;
}

.cs-ghost__bar {
  display: block;
  width: var(--w);
  height: var(--lh-lead);
  border-radius: var(--radius);
  background: var(--c-overlay-hover);
}

.cs-ghost__bar:first-child {
  height: 22px;
}

.cs-ghost__cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(256px, 1fr));
  gap: 24px;
  margin-top: var(--band-gap);
}

.cs-ghost__card {
  display: block;
  height: 320px;
  box-shadow: inset 0 0 0 1px var(--c-line);
}

/* Centred over the stand-in, a quarter of the way down. */
.cs-gate__prompt {
  position: absolute;
  top: 25%;
  left: var(--pad);
  right: var(--pad);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  max-width: 560px;
  margin-inline: auto;
  text-align: center;
}

/* Figma "Enter Password Scrim": the page frosted behind a 64px blur, the
   form card centred. */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  background: var(--c-overlay);
  -webkit-backdrop-filter: blur(64px);
  backdrop-filter: blur(64px);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
}

.scrim[hidden] {
  display: none;
}

.scrim.is-open {
  opacity: 1;
}

.is-gated {
  overflow: hidden;
}

.scrim__container {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 24px var(--pad);
  overflow-y: auto;
  /* Scrolling the card never carries on to the page behind. */
  overscroll-behavior: contain;
}

.gate-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 32px;
  width: 100%;
  min-width: 256px;
  max-width: 480px;
  margin: auto;
  padding: 24px;
  background: var(--c-bg);
  box-shadow: inset 0 0 0 1px var(--c-line);
  transform: translate3d(0, 12px, 0);
  transition: transform 0.5s var(--ease-out-expo, var(--ease));
}

/* Inner padding: 24 on phones, 32 on tablets, 48 on desktop. The close
   button sits 16 / 24 / 32 in from the card's corner to match. */
@media (min-width: 768px) {
  .gate-card {
    padding: 32px;
  }
}

@media (min-width: 1200px) {
  .gate-card {
    padding: 48px;
  }
}

.scrim.is-open .gate-card {
  transform: none;
}

.gate-card__close {
  position: absolute;
  top: 16px;
  right: 16px;
}

@media (min-width: 768px) {
  .gate-card__close {
    top: 24px;
    right: 24px;
  }
}

@media (min-width: 1200px) {
  .gate-card__close {
    top: 32px;
    right: 32px;
  }
}

.gate-card__header {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Title Header/Base */
.gate-card__title {
  padding-right: 48px; /* clear of the close button */
  font-family: var(--f-display);
  font-weight: 600;
  font-size: 40px;
  line-height: 48px;
  color: var(--c-text);
}

/* Body/Base */
.gate-card__desc,
.gate-card__request {
  font-size: 16px;
  line-height: 30px;
  color: var(--c-text-2);
}

/* Figma `request-access`: wraps, 2 / 8 apart, centred. */
.gate-card__request {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2px 8px;
  text-align: center;
}

.gate-card__link {
  color: var(--c-text);
  text-decoration: none;
}

.gate-card__link:hover,
.gate-card__link:focus-visible {
  text-decoration: underline;
  text-underline-offset: 4px;
}

.gate-card__submit {
  width: 100%;
  justify-content: center;
}

.gate-card.is-busy .gate-card__submit {
  opacity: 0.6;
  cursor: progress;
}

/* Figma `.input` */
.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Body Strong/Small */
.field__label {
  font-size: 14px;
  line-height: 24px;
  font-weight: 500;
  color: var(--c-text-2);
}

.field__control {
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 64px;
  padding: 8px 12px 8px 16px;
  border: 1px solid var(--c-line);
  border-radius: var(--radius);
  background: var(--c-overlay);
  transition: border-color var(--dur) var(--ease);
}

/* Phones and tablets: the field is as tall as the CTA button under it
   (--cta-h, the 48px medium size). */
@media (max-width: 1199px) {
  .field__control {
    min-height: 0;
    height: var(--cta-h);
    padding: 0 4px 0 16px;
  }
}

.field__control:focus-within {
  border-color: var(--c-text-2);
}

.has-error .field__control {
  border-color: #e5484d;
}

.field__input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 16px;
  line-height: 30px;
  color: var(--c-text);
  outline: none;
}

.field__error {
  min-height: 0;
  font-size: 14px;
  line-height: 24px;
  color: #e5484d;
}

.field__error:empty {
  display: none;
}

/* ---------- Cookie notice ------------------------------------------------ */
/* First visit only (js/site.js). Bottom right from 768, full width above the
   bottom gutter on phones; above the edge blur, below the password scrim. */
.cookie-notice {
  position: fixed;
  left: var(--pad);
  right: var(--pad);
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  z-index: 45;
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 400px;
  margin-left: auto;
  padding: 24px;
  background: var(--c-bg);
  box-shadow:
    inset 0 0 0 1px var(--c-line),
    0 2px 3px rgba(0, 0, 0, 0.06),
    0 8px 12px rgba(0, 0, 0, 0.12);
  opacity: 0;
  transform: translate3d(0, 12px, 0);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}

.cookie-notice[hidden] {
  display: none;
}

.cookie-notice.is-in {
  opacity: 1;
  transform: none;
}

@media (min-width: 768px) {
  .cookie-notice {
    left: auto;
    right: var(--pad);
    bottom: 24px;
  }
}

/* Body/Small */
.cookie-notice__text {
  font-size: 14px;
  line-height: 24px;
  color: var(--c-text-2);
}

.cookie-notice__actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.cookie-notice__btn {
  height: 40px;
  padding-inline: 16px;
}

/* ---------- Lightbox — js/lightbox.js ------------------------------------ */
/* The password scrim's frosted ground, with the figure centred, its caption
   under it, and previous / count / next along the bottom. */
[data-lightbox] {
  cursor: zoom-in;
}

[data-lightbox]:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 4px;
}

.lightbox {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 72px var(--pad) 24px;
  /* Much denser than the password scrim: the page behind a lightbox is often
     a dark (or, in dark mode, bright) image. At 94% the ground stays within
     a few percent of the page colour whatever is behind it, so the caption
     and counter below (--c-link, 90%) clear WCAG AA (4.5:1) in both themes;
     worst case is about 9:1. */
  background: color-mix(in srgb, var(--c-bg) 94%, transparent);
}

.lightbox__close {
  position: absolute;
  top: 16px;
  right: var(--pad);
}

.lightbox__stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 100%;
  max-width: var(--page-max);
  min-height: 0;
  flex: 1 1 auto;
  margin: 0;
}

.lightbox__frame {
  display: flex;
  justify-content: center;
  width: 100%;
  flex: none;
  /* js/lightbox.js handles pinch, pan and double-tap itself. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.lightbox__img {
  cursor: zoom-in;
}

.lightbox.is-zoomed .lightbox__img {
  cursor: grab;
}

.lightbox.is-zoomed .lightbox__frame:active .lightbox__img {
  cursor: grabbing;
}

/* The zoomed picture passes over the caption; the controls stay on top. */
.lightbox__stage {
  position: relative;
  z-index: 1;
}

.lightbox__close,
.lightbox__nav {
  z-index: 2;
}

.lightbox__nav {
  position: relative;
}

.lightbox.is-zoomed .lightbox__caption {
  opacity: 0;
}

.lightbox__caption {
  transition: opacity var(--dur) var(--ease);
}

.lightbox__img {
  display: block;
  max-width: 100%;
  /* Room above for the close button, below for the caption and controls. */
  max-height: calc(100svh - 280px);
  object-fit: contain;
  border-radius: var(--radius);
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.06), 0 8px 12px rgba(0, 0, 0, 0.12);
  transform: scale(0.97);
  transition: transform 0.45s var(--arrow-ease);
}

.lightbox.is-open .lightbox__img {
  transform: none;
}

/* Code/Small, a step up from the page caption so it reads at a distance. */
.lightbox__caption {
  flex: none;
  width: 100%;
  max-width: 880px;
  font-family: var(--f-mono);
  font-size: 12px;
  line-height: 20px;
  text-align: center;
  color: var(--c-link);
}

.lightbox__nav {
  flex: none;
  display: flex;
  align-items: center;
  gap: 16px;
}

.lightbox__nav [hidden] {
  display: none;
}

.lightbox__count {
  min-width: 56px;
  font-family: var(--f-mono);
  font-size: 12px;
  line-height: 20px;
  text-align: center;
  color: var(--c-link);
  font-variant-numeric: tabular-nums;
}

.lightbox__prev .icon,
.lightbox__next .icon {
  transition: transform var(--arrow-dur) var(--arrow-ease);
}

.lightbox__prev:hover .icon,
.lightbox__prev:focus-visible .icon {
  transform: translateX(-3px);
}

.lightbox__next:hover .icon,
.lightbox__next:focus-visible .icon {
  transform: translateX(3px);
}

/* Phones held sideways: too short for the stacked layout (the image was
   left with ~110px). The image takes the height, the arrows move to the
   sides, and the caption keeps to one line. */
@media (orientation: landscape) and (max-height: 540px) {
  .lightbox {
    gap: 8px;
    padding: 12px 72px;
  }
  .lightbox__close {
    top: 12px;
    right: 16px;
  }
  .lightbox__stage {
    gap: 8px;
  }
  .lightbox__img {
    /* 24px padding, 20px caption, 20px counter, two 8px gaps */
    max-height: calc(100svh - 80px);
    transform: none;
  }
  .lightbox__caption {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  .lightbox__nav {
    position: static;
  }
  .lightbox__prev,
  .lightbox__next {
    position: absolute;
    z-index: 2;
    top: 50%;
    margin-top: -20px;
  }
  .lightbox__prev {
    left: 16px;
  }
  .lightbox__next {
    right: 16px;
  }
}

/* Moving between images: the new one slides in from the side it came from. */
.lightbox__stage.is-from-right {
  animation: lightbox-from-right 0.4s var(--arrow-ease);
}

.lightbox__stage.is-from-left {
  animation: lightbox-from-left 0.4s var(--arrow-ease);
}

@keyframes lightbox-from-right {
  from { opacity: 0; transform: translate3d(24px, 0, 0); }
  to   { opacity: 1; transform: none; }
}

@keyframes lightbox-from-left {
  from { opacity: 0; transform: translate3d(-24px, 0, 0); }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   Motion — js/motion.js
   `motion` is set on <html> before first paint when the reader allows
   motion; nothing below applies otherwise.
   ========================================================================== */

:root {
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}

/* Hold the page until motion.js has tagged what it animates, so nothing in
   the first screen flashes in and then out. */
.motion:not(.motion-ready) main,
.motion:not(.motion-ready) .footer {
  opacity: 0;
}

.motion [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 24px, 0);
  filter: blur(8px);
  transition:
    opacity 0.9s var(--ease-out-expo),
    transform 0.9s var(--ease-out-expo),
    filter 0.9s var(--ease-out-expo);
  transition-delay: var(--d, 0ms);
}

.motion [data-reveal="load"] {
  transform: translate3d(0, 12px, 0);
}

.motion [data-reveal].is-in {
  opacity: 1;
  transform: none;
  filter: none;
}

/* The diagonal texture drifts on its own, one pattern repeat every 2s, so
   the loop is seamless. A transform, so phones run it on the GPU. */
.motion .hatch::before {
  will-change: transform;
  animation: hatch-drift 2s linear infinite;
}

@keyframes hatch-drift {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(12.4847px, 0, 0);
  }
}

/* Hero headline: each word sharpens in turn; the hatch fades up behind. */
.motion .m-split .m-word {
  display: inline-block;
  opacity: 0;
  transform: translate3d(0, 0.18em, 0);
  filter: blur(12px);
  transition:
    opacity 1s var(--ease-out-expo),
    transform 1s var(--ease-out-expo),
    filter 1s var(--ease-out-expo);
  transition-delay: var(--d, 0ms);
}

.motion .m-split.is-in .m-word {
  opacity: 1;
  transform: none;
  filter: none;
}

.motion .m-split.hatch::before {
  opacity: 0;
  transition: opacity 1.4s ease var(--d, 0ms);
}

.motion .m-split.hatch.is-in::before {
  opacity: 1;
}

/* Statement text lights word by word as it scrolls through. */
.motion .m-scrub .s-word {
  opacity: 0.2;
  transition: opacity 0.35s ease;
}

.motion .m-scrub .s-word.is-lit {
  opacity: 1;
}

/* ---------- Edge blur ---------------------------------------------------- */
/* A progressive blur along the bottom of the viewport: eight layers, each
   twice as strong as the last, masked into overlapping bands so content
   softens as it reaches the edge. Fades out at the end of the page so the
   footer's last line stays legible. */

.edge-blur {
  --edge-h: 80px;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  height: var(--edge-h);
  pointer-events: none;
  transition: opacity 0.4s ease;
}

@media (min-width: 768px) {
  .edge-blur {
    --edge-h: 104px;
  }
}

@media (min-width: 1200px) {
  .edge-blur {
    --edge-h: 120px;
  }
}

.edge-blur.is-end {
  opacity: 0;
}

/* Touch devices get no edge blur. Safari 26 paints a solid bar under its
   floating toolbar whenever a blur touches the bottom edge (tested on an
   iPhone: gone once the blur stopped short of the edge), and the browser's
   own glass toolbar already softens the page there. */
@media (hover: none) and (pointer: coarse) {
  .edge-blur {
    display: none;
  }
}

.edge-blur > span {
  position: absolute;
  inset: 0;
  -webkit-backdrop-filter: blur(var(--b));
  backdrop-filter: blur(var(--b));
  -webkit-mask-image: linear-gradient(to bottom, transparent var(--m0), #000 var(--m1), #000 var(--m2), transparent var(--m3));
  mask-image: linear-gradient(to bottom, transparent var(--m0), #000 var(--m1), #000 var(--m2), transparent var(--m3));
}

.edge-blur > span:nth-child(1) { --b: 0.137px; --m0: 0%;    --m1: 12.5%; --m2: 25%;    --m3: 37.5%; }
.edge-blur > span:nth-child(2) { --b: 0.273px; --m0: 12.5%; --m1: 25%;   --m2: 37.5%;  --m3: 50%; }
.edge-blur > span:nth-child(3) { --b: 0.547px; --m0: 25%;   --m1: 37.5%; --m2: 50%;    --m3: 62.5%; }
.edge-blur > span:nth-child(4) { --b: 1.094px; --m0: 37.5%; --m1: 50%;   --m2: 62.5%;  --m3: 75%; }
.edge-blur > span:nth-child(5) { --b: 2.188px; --m0: 50%;   --m1: 62.5%; --m2: 75%;    --m3: 87.5%; }
.edge-blur > span:nth-child(6) { --b: 4.375px; --m0: 62.5%; --m1: 75%;   --m2: 87.5%;  --m3: 100%; }
.edge-blur > span:nth-child(7) { --b: 8.75px;  --m0: 75%;   --m1: 87.5%; --m2: 100%;   --m3: 112.5%; }
.edge-blur > span:nth-child(8) { --b: 17.5px;  --m0: 87.5%; --m1: 100%;  --m2: 112.5%; --m3: 125%; }

/* ---------- Scroll prompt ------------------------------------------------ */
/* "Scroll to explore": the eyebrow and its down arrow with a highlight
   sweeping across both. A second, full-strength copy sits on top and is
   shown through a moving gradient mask, which works for the icon as well as
   the text. The whole prompt is aria-hidden, so the copy is never read. */

.scroll-prompt {
  display: inline-grid;
}

.scroll-prompt__row {
  grid-area: 1 / 1;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.scroll-prompt__icon {
  flex: none;
  width: 8px;
  height: 10px;
  fill: currentColor;
}

.scroll-prompt__shine {
  color: var(--c-text);
  pointer-events: none;
  -webkit-mask: linear-gradient(100deg, transparent 35%, #000 50%, transparent 65%) 0 0 / 300% 100% no-repeat;
  mask: linear-gradient(100deg, transparent 35%, #000 50%, transparent 65%) 0 0 / 300% 100% no-repeat;
  animation: scroll-prompt-shimmer 2.8s linear infinite;
}

@keyframes scroll-prompt-shimmer {
  from { -webkit-mask-position: 100% 0; mask-position: 100% 0; }
  to   { -webkit-mask-position: 0% 0;   mask-position: 0% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-prompt__shine {
    display: none;
  }
}

/* ---------- Full-bleed divider rules ------------------------------------ */

/* Divider rules run past the content column toward the edges of the screen,
   as the Figma line does. A border-image outset paints outside the box
   without adding to the scrollable width. Rules inside cards stay put.
   Kept last: the rules' own `border` shorthand would reset border-image. */
.section-label,
.skill-list__rule,
.cs-rule:not(.cs-panel .cs-rule) {
  /* The Figma line is 1440 wide, centred: out to the screen edges up to a
     1440 screen, and no wider than 1440 beyond that. */
  --rule-outset: max(0px, calc((min(100vw, var(--page-max)) - min(100vw - 2 * var(--pad), var(--page-max))) / 2));
  border-image: linear-gradient(var(--c-line), var(--c-line)) 1 / 1px 0 0 0 / 0 var(--rule-outset);
  overflow: visible; /* <hr> clips its overflow by default */
}

/* ---------- Arrow motion ------------------------------------------------- */

/* Every arrow — buttons, text links, social links, cards, menus — turns and
   steps on one longer, softer curve. Kept last so it wins over each
   component's own transition. */
.icon:has(> svg > use[href="#i-arrow-ur"]) {
  transition: transform var(--arrow-dur) var(--arrow-ease);
  will-change: transform;
}

/* ---------- Image protection --------------------------------------------- */
/* Pairs with js/site.js: no dragging images out, no iOS long-press
   "Save image" sheet, no selecting them as part of a text selection. */
img,
video {
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
  user-select: none;
}

/* ---------- Icons on the page ground ------------------------------------- */
/* Arrows that sit on the page itself (not on ink, like the footer and the
   primary buttons) follow the ghost icon colour: ink in light mode, lime in
   dark, so they stay legible on white. */
.social-link .icon,
.btn-text .icon,
.btn--tertiary .icon {
  color: var(--c-btn-icon);
}

/* ---------- Loading dots -------------------------------------------------- */
/* "Opening email client..." — the three dots pulse in turn. */
.dots {
  display: inline-flex;
  margin-left: 1px;
}

.dots span {
  animation: dot-pulse 1.2s var(--ease) infinite;
}

.dots span:nth-child(2) { animation-delay: 0.2s; }
.dots span:nth-child(3) { animation-delay: 0.4s; }

@keyframes dot-pulse {
  0%, 80%, 100% { opacity: 0.2; }
  40%           { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .dots span {
    animation: none;
  }
}

/* ---------- Local time --------------------------------------------------- */
/* "Columbus, Ohio 04:41 AM" — the time takes the eyebrow's own type and
   colour, with z's that drift up overnight. */
.local-time {
  margin-left: 4px;
}

.local-time[hidden],
.zzz[hidden] {
  display: none;
}

.zzz {
  display: inline-flex;
  margin-left: 4px;
  text-transform: none;
}

.zzz span {
  display: inline-block;
  opacity: 0;
  animation: zzz 2.4s var(--ease) infinite;
}

.zzz span:nth-child(1) { font-size: 0.8em; }
.zzz span:nth-child(2) { font-size: 0.95em; animation-delay: 0.4s; }
.zzz span:nth-child(3) { font-size: 1.1em; animation-delay: 0.8s; }

@keyframes zzz {
  0%   { opacity: 0; transform: translate3d(0, 2px, 0); }
  30%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(2px, -6px, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .zzz span {
    animation: none;
    opacity: 1;
  }
}

/* On phones the label row wraps; the scroll prompt keeps to the right edge
   of its own line. (Only here: on one line, the auto margin would pull the
   other labels together.) */
@media (max-width: 767px) {
  .section-label > .scroll-prompt,
  .hero__foot > .scroll-prompt {
    margin-left: auto;
  }
}
