/* Layer order (PLAN §5). site.11ty.js joins these imports into /assets/css/site.css at build time. */
@layer reset, tokens, themes, base, components, theme-extras, utilities;

/* Self-hosted, OFL. Files from Fontsource 5.3.0. Latin Extended loads only for characters like "ć". */

@font-face {
  font-family: "Schibsted Grotesk";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("/assets/fonts/schibsted-grotesk-latin-wght-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Schibsted Grotesk";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("/assets/fonts/schibsted-grotesk-latin-ext-wght-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Schibsted Grotesk";
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url("/assets/fonts/schibsted-grotesk-latin-wght-italic.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Schibsted Grotesk";
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url("/assets/fonts/schibsted-grotesk-latin-ext-wght-italic.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono-latin-wght-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono-latin-ext-wght-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

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

* { margin: 0; }

html {
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
}

body {
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
}

img, picture, svg, video { display: block; max-width: 100%; }
img { height: auto; }

input, button, textarea, select { font: inherit; }

p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }

}
@layer tokens {
/* Primitives: raw values with no meaning. Components never use these directly; themes map them to semantic tokens. */
:root {
  /* Palette */
  --white: #ffffff;
  --paper-50: #f6f6f3;
  --grey-100: #ecece8;
  --grey-200: #dcdcd6;
  --grey-400: #9b9ea6;
  --grey-600: #5b5e66;
  --ink-800: #2b2d32;
  --ink-850: #1a1b1f;
  --ink-900: #16171a;
  --ink-950: #121316;
  --redline-300: #ff74b0;
  --redline-600: #c2206e;

  /* Spacing: 4px scale */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-24: 96px;

  /* Type scale: fluid between 375px and 1280px viewports */
  --step--2: 0.75rem;
  --step--1: 0.8125rem;
  --step-0: 1rem;
  --step-1: clamp(1.125rem, 1.05rem + 0.33vw, 1.3125rem);
  --step-2: clamp(1.25rem, 1.08rem + 0.75vw, 1.75rem);
  --step-3: clamp(1.625rem, 1.33rem + 1.25vw, 2.375rem);
  --step-4: clamp(2.75rem, 1.86rem + 3.8vw, 4.75rem);
  --step-5: clamp(2.75rem, 14vw, 10.5rem);

  /* Fonts */
  --font-grotesk: "Schibsted Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-jetbrains: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* Radii */
  --radius-0: 0;
  --radius-1: 3px;
  --radius-full: 999px;

  /* Motion */
  --duration-1: 150ms;
  --duration-2: 300ms;
  --duration-3: 600ms;
  --duration-morph: 350ms;
  --duration-morph-short: 250ms;
  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-in-out: cubic-bezier(0.6, 0, 0.2, 1);
}

}
@layer themes {
/* Clean theme (direction: Redline, notes/direction.md). Follows the OS light/dark setting. */
:root,
[data-theme="clean"] {
  color-scheme: light dark;

  --color-bg: light-dark(var(--paper-50), var(--ink-950));
  --color-surface: light-dark(var(--white), var(--ink-850));
  --color-text: light-dark(var(--ink-900), var(--grey-100));
  --color-text-muted: light-dark(var(--grey-600), var(--grey-400));
  --color-border: light-dark(var(--grey-200), var(--ink-800));
  --color-rule-strong: var(--color-text);
  --color-accent: light-dark(var(--redline-600), var(--redline-300));
  --color-on-accent: light-dark(var(--white), var(--ink-950));
  --color-plate: transparent;
  --color-grid: light-dark(rgb(194 32 110 / 0.04), rgb(255 116 176 / 0.05));

  --focus-ring: 2px solid var(--color-accent);

  --font-display: var(--font-grotesk);
  --font-body: var(--font-grotesk);
  --font-mono: var(--font-jetbrains);

  --icon-stroke: 2;
  --shadow-card: none;
  --radius-card: var(--radius-0);
  --measure: 42rem;
  --prose-size: 1.125rem;
  --card-border-width: 1px;
  --card-padding: var(--space-5);

  --motion-duration: var(--duration-2);
  --motion-draw-duration: var(--duration-3);
  --motion-fade-duration: var(--duration-1);
  --motion-morph-duration: var(--duration-morph);
  --motion-ease: var(--ease-out);
}

[data-theme] {
  background: var(--color-bg);
  color: var(--color-text);
}

}
/* Comic theme: cel-shaded graphic novel. Flat mid-tones, hard shadow shapes, ink hatching, slightly wobbly lines.
   Same markup as Clean.
   Style only: no game's art, logos or fonts. */

@font-face {
  font-family: "Bangers";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/bangers-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Bangers";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/bangers-latin-ext-400-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@layer themes {
  [data-theme="comic"] {
    color-scheme: light;

    --color-bg: #e8d6b0;
    --color-surface: #fbf1dc;
    --color-ink: #14110f;
    --color-text: var(--color-ink);
    --color-text-muted: #4a4136;
    --color-border: var(--color-ink);
    --color-rule-strong: var(--color-ink);
    --color-accent: #a32c0a;
    --color-on-accent: #ffffff;
    --color-plate: #f4e6c8;
    --color-grid: rgb(163 44 10 / 0.1);
    --color-shade: #e2c48c;
    --color-sky: #f08a3c;
    --color-sky-shade: #d9622b;
    --color-caption: #f7cf4a;

    --font-lettering: "Bangers", var(--font-grotesk);
    --font-display: var(--font-lettering);
    --font-body: var(--font-grotesk);
    --font-mono: var(--font-grotesk); /* Labels. Bangers is too hard to read this small. */

    --focus-ring: 3px solid var(--color-accent);
    --icon-stroke: 3;
    --shadow-card: 6px 6px 0 var(--color-ink);
    --radius-card: var(--radius-0);
    --measure: 42rem;
    --prose-size: 1.125rem;
    --card-border-width: var(--ink-width);
    --card-padding: var(--space-5);

    --motion-duration: var(--duration-1);
    --motion-draw-duration: var(--duration-2);
    --motion-fade-duration: var(--duration-1);
    --motion-morph-duration: var(--duration-morph-short);
    --motion-ease: var(--ease-out);

    /* Comic dials. */
    --ink-width: 3px;
    --ink-outline:
      drop-shadow(2px 0 0 var(--color-ink)) drop-shadow(-2px 0 0 var(--color-ink))
      drop-shadow(0 2px 0 var(--color-ink)) drop-shadow(0 -2px 0 var(--color-ink));
    --panel-radius: 255px 15px 225px 15px / 15px 225px 15px 255px;
    --burst: polygon(50% 0, 61% 18%, 79% 6%, 79% 27%, 100% 25%, 87% 43%, 100% 61%, 80% 63%, 84% 86%, 64% 76%, 50% 100%, 38% 78%, 16% 88%, 20% 64%, 0 60%, 14% 44%, 0 24%, 21% 27%, 22% 6%, 39% 18%);
    --hatch: repeating-linear-gradient(-60deg, var(--color-ink) 0 2px, transparent 2px 9px);
  }
}

@layer theme-extras {
  [data-theme="comic"] {
    /* Lettering. Bangers has one weight and its own slant, so no faux bold or italic. */
    :is(.hero__name, .case-header__title, .case-card__title, .next-case__title, .stat__value, .prose h2, .site-header__name) {
      font-family: var(--font-lettering);
      font-weight: 400;
      font-style: normal;
      letter-spacing: 0.03em;
    }

    /* Panels */
    :is(.case-card, .hero__inner, .case-header, .next-case, .flow__box) {
      border-radius: var(--panel-radius);
    }
    .case-card { overflow: hidden; }

    /* Yellow caption boxes */
    :is(.section-head__title, .case-card__meta, .next-case__label, .case-header__back) {
      width: fit-content;
      padding: 3px 8px;
      background: var(--color-caption);
      border: 2px solid var(--color-ink);
      color: var(--color-ink);
      font-weight: 700;
    }
    .case-card__meta span + span::before { color: var(--color-ink); }

    /* Header */
    .site-header__inner { border-bottom: var(--ink-width) solid var(--color-ink); }
    .site-header__name { font-size: var(--step-2); }
    .site-header__links {
      gap: var(--space-2);

      a {
        display: block;
        padding: 2px var(--space-2);
        background: var(--color-surface);
        border: 2px solid var(--color-ink);
        box-shadow: 2px 2px 0 var(--color-ink);
        color: var(--color-ink);
        font-weight: 700;

        &[aria-current] { background: var(--color-caption); }

        @media (hover: hover) {
          &:hover { background: var(--color-caption); }
        }
      }
    }

    /* Big panels: flat colour, one hard shadow shape, ink hatching in the shadow. */
    :is(.hero__inner, .case-header, .next-case) {
      --focus-ring: 3px solid var(--color-ink); /* The accent is too close to the panel's orange. */

      position: relative;
      background: linear-gradient(105deg, var(--color-sky) 0 64%, var(--color-sky-shade) 64%);
      border: var(--ink-width) solid var(--color-ink);
      box-shadow: var(--shadow-card);

      &::before {
        content: "";
        position: absolute;
        inset: 0;
        pointer-events: none;
        background: var(--hatch);
        border-radius: inherit;
        mask-image: linear-gradient(105deg, transparent 84%, #000 98%); /* Phones: text runs wide, so only the corner. */

        @media (min-width: 900px) { mask-image: linear-gradient(105deg, transparent 66%, #000 92%); }
      }
    }
    :is(.hero__name, .case-header__title, .next-case__title) {
      color: #ffffff;
      line-height: 1.05;
      -webkit-text-stroke: 0.06em var(--color-ink);
      paint-order: stroke fill;
      text-shadow: 0.05em 0.05em 0 var(--color-ink);
    }

    /* Hero */
    .hero__inner {
      margin-block: var(--space-4) var(--space-5);
      padding: var(--space-5) var(--space-4) var(--space-8);

      @media (min-width: 900px) {
        margin-block: var(--space-6);
        padding: var(--space-12) var(--space-10) var(--space-16);
      }
    }
    /* The grid is aria-hidden, so the sound effect stays out of the reading order. */
    .hero__grid {
      display: block;
      mask-image: none;
      span { display: none; }

      &::before,
      &::after {
        position: absolute;
        right: var(--space-2);
        bottom: var(--space-2);
        display: grid;
        width: 6.5rem;
        aspect-ratio: 5 / 4;
        clip-path: var(--burst);
        rotate: -8deg;

        @media (min-width: 900px) {
          right: 5%;
          bottom: 9%;
          width: clamp(14rem, 24vw, 19rem);
        }
      }
      &::before {
        content: "";
        background: var(--color-ink);
      }
      &::after {
        content: "POW!";
        place-items: center;
        background: linear-gradient(115deg, var(--color-caption) 0 60%, #e8a72a 60%);
        color: #ffffff;
        font-family: var(--font-lettering);
        font-size: 1.5rem;
        letter-spacing: 0.03em;
        -webkit-text-stroke: 0.1em var(--color-ink);
        paint-order: stroke fill;
        scale: 0.92;

        @media (min-width: 900px) { font-size: clamp(3rem, 5.5vw, 4.5rem); }
      }
    }
    .hero__role {
      width: fit-content;
      padding: 3px 8px;
      background: var(--color-ink);
      color: #ffffff;
      font-size: var(--step--2);
      font-weight: 700;
      letter-spacing: 0.04em;
      white-space: nowrap;
    }
    .hero__lede {
      font-size: var(--step-2);
      font-weight: 700;
      letter-spacing: 0;

      @media (min-width: 900px) { font-size: var(--step-3); }
    }

    /* The hero's one caption. The number is still the real token; only the words around it are theme text. */
    .hero__measure {
      &::before {
        border-color: var(--color-ink);
        background-image: linear-gradient(var(--color-ink), var(--color-ink));
      }
      span {
        padding: 2px 8px 3px;
        background: var(--color-surface);
        border: 2px solid var(--color-ink);
        border-radius: 0;
        color: var(--color-ink);
        font-family: var(--font-lettering);
        font-size: var(--step-0);
        font-weight: 400;
        letter-spacing: 0.04em;

        &::before { content: "← "; }
        &::after { content: "px of breathing room!"; }
      }
    }

    /* Speech bubbles */
    :is(.hero__tagline, .callout, .quote) {
      position: relative;
      width: fit-content;
      padding: var(--space-3) var(--space-5);
      background: var(--color-surface);
      border: var(--ink-width) solid var(--color-ink);
      border-radius: 1.75em;

      &::before,
      &::after {
        content: "";
        position: absolute;
        left: 2.5em;
        width: 26px;
        height: 20px;
        background: var(--color-ink);
        clip-path: polygon(0 0, 100% 0, 8% 100%);
        animation: none;
      }
      &::before { top: 100%; }
      &::after {
        top: calc(100% - 5px);
        left: calc(2.5em + 4px);
        width: 18px;
        height: 16px;
        background: var(--color-surface);
      }
    }
    .hero__tagline {
      font-family: var(--font-lettering);
      margin-top: var(--space-4);
      padding: var(--space-2) var(--space-4);
      font-size: var(--step-2);
      font-style: normal;
      font-weight: 400;
      letter-spacing: 0.04em;
    }
    .quote { margin-block: var(--space-4) var(--space-6); }
    /* Phones: the longer line wraps in the bubble and runs into the burst, so the edit only plays from 900px. */
    .hero__cut { @media (max-width: 899px) { display: none; } }

    /* Section heads: the caption box hangs on the rule. */
    .section-head {
      border-top-width: var(--ink-width);
      padding-top: 0;
    }
    .section-head__title {
      margin-top: -13px;
      font-family: var(--font-lettering);
      font-size: var(--step-1);
      font-weight: 400;
      line-height: 1.1;
    }
    .section-head__note {
      padding-top: var(--space-2);
      color: var(--color-ink);
      font-weight: 700;
    }

    /* Case cards */
    .case-list { gap: var(--space-8); }
    .case-card {
      transform: translate(calc(-3px * var(--card-active)), calc(-3px * var(--card-active)));
      box-shadow: calc(6px + 4px * var(--card-active)) calc(6px + 4px * var(--card-active)) 0 var(--color-ink);
      transition: transform var(--motion-duration) var(--motion-ease), box-shadow var(--motion-duration) var(--motion-ease);

      /* Pressed: the panel lands on its shadow. */
      &:active {
        transform: translate(6px, 6px);
        box-shadow: 0 0 0 var(--color-ink);
        transition: none;
      }
    }
    .case-card__deco { display: none; } /* The panel lifting is the hover state here. */
    .case-card__body {
      background: linear-gradient(112deg, var(--color-surface) 0 78%, var(--color-shade) 78%);
    }
    .case-card__summary { color: var(--color-ink); }
    .case-card__title .icon { color: var(--color-ink); }

    /* Plates. Halftone and hatching stay behind images and never touch text. */
    :is(.case-card__media, .case-header__media) {
      background:
        radial-gradient(circle, rgb(20 17 15 / 0.12) 1.2px, transparent 1.6px) 0 0 / 7px 7px,
        var(--color-plate);
    }
    .case-card__media {
      background:
        repeating-linear-gradient(-60deg, var(--color-shade) 0 2px, transparent 2px 9px) right bottom / 40% 24% no-repeat,
        var(--color-plate);
    }
    .case-header__media {
      border: var(--ink-width) solid var(--color-ink);
      border-radius: var(--panel-radius);
      box-shadow: var(--shadow-card);
    }

    /* Case page */
    /* Case header: the same big panel as the hero, so a case opens with a splash page. */
    .case-header {
      padding: var(--space-5) var(--space-4) var(--space-6);

      > * { position: relative; }
      @media (hover: hover) {
        a:hover { color: var(--color-ink); }
      }

      @media (min-width: 900px) { padding: var(--space-8); }
    }
    .case-header__summary { font-weight: 600; }
    .case-header__facts {
      border-top-width: var(--ink-width);
      dt { color: var(--color-ink); font-weight: 700; }
    }

    /* Stats: small panels. */
    .stat {
      padding: var(--space-3) var(--space-4);
      background: var(--color-surface);
      border: var(--ink-width) solid var(--color-ink);
      box-shadow: 3px 3px 0 var(--color-ink);
    }
    .stat__value { color: var(--color-accent); }
    .stat__label { color: var(--color-ink); font-weight: 600; }

    /* Reading text stays calm: only the emphasis gets a marker stroke. */
    .prose :is(a) { text-decoration-thickness: 2px; }
    .prose strong { background: linear-gradient(transparent 62%, var(--color-caption) 62%); }

    /* Section headings: black tabs on an ink rule that shades off into hatching. */
    .prose > h2 {
      align-self: start;
      width: fit-content;
      padding: var(--space-2) var(--space-3);
      background: var(--color-ink);
      color: #ffffff;
      line-height: 1.05;
    }
    .prose > h3 {
      align-self: start;
      width: fit-content;
      padding: 3px 8px;
      background: var(--color-caption);
      border: 2px solid var(--color-ink);
    }
    @media (min-width: 900px) {
      :is(.case, .page--rail) > .prose > :is(h2, h3)::before { border-top-width: var(--ink-width); }
      :is(.case, .page--rail) > .prose > h2 {
        padding: var(--space-2) var(--space-3);

        &::after {
          content: "";
          position: absolute;
          top: 0;
          left: 0;
          width: 100cqw;
          height: var(--space-3);
          background: var(--hatch);
          mask-image: linear-gradient(90deg, transparent 72%, #000);
        }
      }
      :is(.case, .page--rail) > .prose > h3 {
        padding: 3px 8px;
        font-weight: 700;

        &::before { top: -2px; left: -2px; border-color: var(--color-ink); }
      }
    }

    /* The cut-out on a rail page: inked like the illustrations, on an ink line. */
    .page--rail > .page-header:has(.page-header__media) {
      &::after { border-top-width: var(--ink-width); }
      .page-header__media { filter: var(--ink-outline); }
    }

    /* The measure, drawn by hand: an arrow in place of the bracket, and a caption around the real token. */
    @media (min-width: 900px) {
      .page-header__measure {
        &::before { content: none; }

        &::after {
          width: 24px;
          border: 0;
          background: var(--color-ink);
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 96' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4C9 20 15 34 11 50 8 64 14 78 12 92'/%3E%3Cpath d='M4 14 12 3l8 10M4 82l8 11 8-10'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
        }
      }

      .page-header__code {
        margin-left: 0;
        padding: var(--space-1) var(--space-3);
        background: var(--color-surface);
        border: 2px solid var(--color-ink);
        color: var(--color-ink);

        [data-inspect="selector"] { color: var(--color-ink); font-weight: 700; }
      }

      .page-header__value {
        right: calc(var(--space-5) + var(--space-8));
        padding: 2px 8px 3px;
        background: var(--color-caption);
        border: 2px solid var(--color-ink);
        color: var(--color-ink);
        font-family: var(--font-lettering);
        font-size: var(--step-0);
        letter-spacing: 0.04em;
        rotate: -2deg;

        &::after { content: "px of hard-won nothing!"; }
      }

      [data-inspected] { outline-color: var(--color-ink); }
    }

    /* Figures: screens stay untouched inside an ink panel. */
    .figure picture {
      background: var(--color-plate);
      border: var(--ink-width) solid var(--color-ink);
      box-shadow: 5px 5px 0 var(--color-ink);
    }
    /* Captions: narration boxes under the panel. */
    .figure__caption {
      width: fit-content;
      margin-top: var(--space-1);
      padding: 2px 8px;
      background: var(--color-surface);
      border: 2px solid var(--color-ink);
      color: var(--color-ink);
      font-weight: 600;
    }

    /* Illustrations bring their own shape, so they get an ink outline and no panel. */
    :is(.persona, .gallery[data-row]) .figure picture,
    .figure[data-size="small"] picture {
      background: none;
      border: 0;
      box-shadow: none;
    }
    :is(.persona, .gallery[data-row]) .figure img,
    .figure[data-size="small"] img,
    .figure__svg { filter: var(--ink-outline); }

    /* Boxie's SVG parts: gradients become flat cel tones, the soft shadow a hard one. */
    .figure__svg {
      .body-shape { fill: #2f7a6c; }
      .body-inner { fill: #5fb3a1; }
      .bx-shadow { fill: var(--color-ink); fill-opacity: 1; }
    }

    /* Flow */
    .flow {
      --flow-line: var(--ink-width) solid var(--color-ink);
      --flow-head: 0.6rem;
    }
    .flow__box {
      border: var(--ink-width) solid var(--color-ink);
      box-shadow: 3px 3px 0 var(--color-ink);
      font-weight: 600;
    }
    .flow__box--end { background: var(--color-caption); }
    :is(.flow__label, .flow__down) { color: var(--color-ink); font-weight: 700; }

    /* Walkthrough */
    .walkthrough__step {
      border: 2px solid var(--color-ink);
      background: var(--color-surface);
      color: var(--color-ink);
      font-weight: 600;

      &[aria-selected="true"] {
        background: var(--color-caption);
        border-color: var(--color-ink);
      }
    }
    @media (min-width: 900px) {
      .case > .prose > .walkthrough[data-ready] {
        .walkthrough__steps { gap: var(--space-1); }
        .walkthrough__step {
          border: 2px solid var(--color-ink);
          border-left-width: 6px;
          &[aria-selected="true"] { border-left-color: var(--color-accent); }
        }
      }
    }
    .walkthrough__tip { color: var(--color-ink); }

    /* Ends of the page */
    .button[data-variant="external"] {
      padding: var(--space-1) var(--space-3);
      background: var(--color-surface);
      border: 2px solid var(--color-ink);
      box-shadow: 3px 3px 0 var(--color-ink);
      font-weight: 700;

      &:active {
        transform: translate(3px, 3px);
        box-shadow: none;
      }
    }
    .facts dt { color: var(--color-ink); font-weight: 700; }
    /* Next case: the last panel on the page. */
    .next-case {
      padding: var(--space-5);

      a {
        position: relative;
        width: 100%;
      }
    }
    .next-case__title { font-size: var(--step-4); }
    .next-case .icon { color: var(--color-ink); }
    .contact__links a { font-family: var(--font-lettering); font-weight: 400; letter-spacing: 0.03em; }
    .contact__links li + li { border-top-width: 2px; }
    :is(.site-footer__note, .site-footer__privacy) { color: var(--color-ink); }
  }

  /* Theme motion: the caption pops in once to point out the redline it replaces. */
  @media (prefers-reduced-motion: no-preference) {
    [data-theme="comic"] :is(.hero__measure span, .page-header__value) {
      animation: comic-pop var(--motion-duration) var(--ease-out) var(--motion-duration) backwards;
    }
  }

  /* The button's redline wouldn't show on ink either, so it pops once instead. */
  @media (prefers-reduced-motion: no-preference) {
    [data-theme="comic"] [data-show-me][data-trace] {
      animation: comic-step-pop 360ms var(--ease-out) 300ms;

      &::after { content: none; }
    }
  }

  /* Points out the steps: a thin line can't show on ink borders, so the panels themselves kick out one after
     another, then the active one pops once. Same trigger as Clean, and using the steps first cancels it. */
  @media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
    [data-theme="comic"] .case > .prose > .walkthrough[data-draw] {
      .walkthrough__steps::before { content: none; }

      .walkthrough__step {
        animation: comic-step-kick 300ms var(--ease-in-out) calc(var(--step-index) * 80ms);
      }

      [aria-selected="true"] {
        animation:
          comic-step-kick 300ms var(--ease-in-out),
          comic-step-pop 360ms var(--ease-out) 1100ms;
      }
    }
  }
}

@keyframes comic-pop {
  from { opacity: 0; transform: scale(0.6); }
}

@keyframes comic-step-kick {
  50% {
    background: var(--color-caption);
    box-shadow: 4px 4px 0 var(--color-ink);
    transform: translateX(10px);
  }
}

@keyframes comic-step-pop {
  50% {
    box-shadow: 5px 5px 0 var(--color-ink);
    transform: scale(1.06) rotate(-1.5deg);
  }
}

/* Vector theme: phosphor guides, glowing lines on black, the plate as a screen bezel. Same markup as Clean.
   Glow stays on lines and headings. Reading text never glows. */

@layer themes {
  [data-theme="vector"] {
    color-scheme: dark;

    --color-bg: #000000;
    --color-surface: #050b08;
    --color-text: #d6ffe9;
    --color-text-muted: #7fc4a2;
    --color-border: #1f5a40;
    --color-rule-strong: #39ff9c;
    --color-accent: #39ff9c;
    --color-on-accent: #000000;
    --color-plate: #0a0f0c;
    --color-grid: rgb(57 255 156 / 0.2);

    --focus-ring: 2px solid var(--color-accent);

    --font-display: var(--font-jetbrains);
    --font-body: var(--font-grotesk);
    --font-mono: var(--font-jetbrains);

    --icon-stroke: 1.5;
    --shadow-card: 0 0 12px rgb(57 255 156 / 0.2);
    --radius-card: var(--radius-0);
    --measure: 42rem;
    --card-border-width: 1px;
    --card-padding: var(--space-5);

    --motion-duration: var(--duration-1);
    --motion-draw-duration: var(--duration-2);
    --motion-fade-duration: var(--duration-1);
    --motion-morph-duration: var(--duration-morph-short);
    --motion-ease: var(--ease-out);

    /* Vector dials. */
    --glow-text: 0 0 10px rgb(57 255 156 / 0.5);
    --glow-line: 0 0 8px rgb(57 255 156 / 0.7);
    --rule-glow: 0 -7px 8px -7px var(--color-accent);
    --bezel-gap: var(--space-2);
    --bezel-radius: 12px;
    --screen-dim: brightness(0.85);
    --scanlines: repeating-linear-gradient(0deg, rgb(0 0 0 / 0.14) 0 1px, transparent 1px 3px);
    --phosphor-tint: grayscale(1) sepia(1) hue-rotate(88deg) saturate(3) brightness(0.95) drop-shadow(0 0 6px rgb(57 255 156 / 0.45));
  }
}

@layer theme-extras {
  /* Vector: phosphor lines on black. The plate becomes a screen bezel. */
  [data-theme="vector"] {
    /* Lettering: mono at a lighter weight, so the glow doesn't clog it. */
    :is(.hero__name, .case-header__title, .next-case__title, .prose h2, .stat__value) {
      font-weight: 500;
      letter-spacing: -0.03em;
      text-shadow: var(--glow-text);
    }
    :is(.next-case__title, .prose h2, .stat__value, .section-head__title) { color: var(--color-accent); }

    /* Hero: the layout grid becomes phosphor guides, and the redline a lit measure. */
    .hero__grid span {
      background: none;
      border-inline: 1px solid var(--color-grid);
    }
    .hero__measure {
      &::before { filter: drop-shadow(0 0 4px var(--color-accent)); }
      span {
        background: none;
        border: 1px solid var(--color-accent);
        color: var(--color-accent);
        box-shadow: var(--glow-line);
      }
    }
    .hero__tagline::before { box-shadow: var(--glow-line); }

    /* Rules are lit lines. */
    :is(.section-head, .next-case) { box-shadow: var(--rule-glow); }
    [data-show-me][data-trace]::after { box-shadow: 0 0 8px var(--color-accent), inset 0 0 8px var(--color-accent); }
    .case-header__facts { border-top-color: var(--color-border); }

    /* Bezel: the plate is the frame, the image the tube inside it. */
    :is(.case-card__media, .case-header__media, .figure picture) {
      position: relative;
      padding: var(--bezel-gap);
      border-radius: var(--bezel-radius);

      img {
        border-radius: calc(var(--bezel-radius) - 4px);
        filter: var(--screen-dim);
      }
      &::after {
        content: "";
        position: absolute;
        inset: var(--bezel-gap);
        pointer-events: none;
        background: var(--scanlines);
        border-radius: calc(var(--bezel-radius) - 4px);
        box-shadow: inset 0 0 24px rgb(0 0 0 / 0.6);
      }
    }
    :is(.case-header__media, .figure picture) {
      border: 1px solid var(--color-border);
      box-shadow: var(--shadow-card);
    }
    .case-card__media {
      border-radius: 0;
      border-bottom-color: var(--color-accent);
    }

    /* Case cards */
    .case-card {
      border-color: var(--color-accent);
      box-shadow: 0 0 calc(10px + 16px * var(--card-active)) rgb(57 255 156 / calc(0.2 + 0.3 * var(--card-active)));
      transition: box-shadow var(--motion-duration) var(--motion-ease);
    }
    .case-card__title {
      font-weight: 500;
      letter-spacing: -0.03em;
      text-shadow: var(--glow-text);
    }
    .case-card__meta span + span::before { color: var(--color-accent); }
    .case-card__deco {
      &::before {
        background: none;
        box-shadow: inset 0 0 12px rgb(57 255 156 / 0.5);
        opacity: 1;
        mask: none;
      }
      &::after {
        outline-style: solid;
        box-shadow: 0 0 8px var(--color-accent);
      }
    }

    /* The cut-out on a rail page: dimmed like a screen, with a phosphor edge, on a glowing line. */
    .page--rail > .page-header:has(.page-header__media) {
      &::after { box-shadow: var(--rule-glow); }
      .page-header__media { filter: var(--screen-dim) drop-shadow(0 0 5px rgb(57 255 156 / 0.5)); }
    }

    /* Case page */
    .prose a { color: var(--color-accent); }
    .prose strong { color: #ffffff; }
    @media (min-width: 900px) {
      :is(.case, .page--rail) > .prose > h2::before {
        border-top-color: var(--color-accent);
        box-shadow: var(--rule-glow);
      }
    }
    .stat { border-top-color: var(--color-border); }
    .callout {
      border-color: var(--color-accent);
      box-shadow: var(--shadow-card);
    }
    .quote { box-shadow: -6px 0 8px -6px var(--color-accent); }

    /* Flow: the lines carry the signal. */
    .flow { --flow-line: 1px solid var(--color-accent); }
    .flow__box { border-color: var(--color-accent); }
    .flow__box--end {
      box-shadow: var(--shadow-card);
      color: var(--color-accent);
    }

    /* Walkthrough */
    .walkthrough__step[aria-selected="true"] {
      background: rgb(57 255 156 / 0.1);
      color: var(--color-accent);
    }
    .walkthrough__steps::before { box-shadow: var(--glow-line); }

    /* Illustrations aren't screens: no bezel. PNGs can't become outlines in CSS, so they go monochrome. */
    :is(.persona, .gallery[data-row]) .figure picture,
    .figure[data-size="small"] picture {
      padding: 0;
      background: none;
      border: 0;
      box-shadow: none;

      &::after { content: none; }
    }
    :is(.persona, .gallery[data-row]) .figure img,
    .figure[data-size="small"] img {
      border-radius: 0;
      filter: var(--phosphor-tint);
    }

    /* Boxie's SVG parts: redrawn as lit outlines. Black fills keep front shapes over back ones. */
    .figure__svg {
      filter: drop-shadow(0 0 5px rgb(57 255 156 / 0.6));

      :is(path, ellipse) {
        fill: #000000;
        stroke: var(--color-accent);
        stroke-width: 7;
        stroke-linejoin: round;
      }
      :is(.eye-shadow-l, .eye-shadow-r) { display: none; }
      :is(.body-inner, .belly-up) { fill: none; stroke-opacity: 0.45; }
      :is(.iris-l, .iris-r, .mouth, .brow-l, .brow-r) { fill: var(--color-accent); }
      .bx-shadow {
        fill: none;
        stroke-dasharray: 18 22;
        stroke-opacity: 0.5;
      }
    }

    .site-header__inner {
      border-bottom-color: var(--color-accent);
      box-shadow: 0 8px 8px -8px var(--color-accent);
    }
    .site-header__name {
      font-family: var(--font-display);
      font-weight: 500;
      color: var(--color-accent);
      text-shadow: 0 0 10px rgb(57 255 156 / 0.6);
    }
    .site-header__links a {
      font-family: var(--font-mono);
      font-size: var(--step--1);
      letter-spacing: 0.08em;
      text-transform: uppercase;

      @media (hover: hover) {
        &:hover {
          color: var(--color-accent);
          text-shadow: 0 0 8px var(--color-accent);
        }
      }
    }
  }
}

@layer base {
/* Base: element defaults and page frame. Only semantic tokens from here on. */

html {
  background: var(--color-bg);
  color: var(--color-text);
  scroll-padding-top: 5rem; /* Anchors and focus stop clear of the header. */
}

/* site-header.js sets the attribute once the page has loaded. */
@media (prefers-reduced-motion: no-preference) {
  html[data-smooth-scroll] { scroll-behavior: smooth; }
}

body {
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.5;
}

h1, h2, h3 {
  font-family: var(--font-display);
  line-height: 1.15;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

a {
  color: inherit;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}
@media (hover: hover) {
  a:hover { color: var(--color-accent); }
}

:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 3px;
}

.icon {
  display: inline-block;
  vertical-align: -0.125em;
  width: 1em;
  height: 1em;
  flex: none;
  stroke-width: var(--icon-stroke);
}

/* Page frame: one wrapper sets the side gutter for every section. */
.wrapper {
  width: 100%;
  max-width: 1160px;
  margin-inline: auto;
  padding-inline: max(clamp(16px, 4vw, 40px), env(safe-area-inset-left, 0px)) max(clamp(16px, 4vw, 40px), env(safe-area-inset-right, 0px));
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: var(--space-4);
  z-index: 10;
  padding: var(--space-2) var(--space-3);
  background: var(--color-text);
  color: var(--color-bg);
  transform: translateY(-200%);
}
.skip-link:focus { transform: none; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

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

}
@layer components {
/* Site header: name on the left, nav on the right, one quiet rule below.
   site-header.js sets data-reveal: the header then stays at the top of the window, slides away on the way down
   and comes back on the way up, so the navigation is one small gesture away on long pages. `body >` skips the
   styleguide sample. A keyboard user tabbing to a link always brings it back. */
body > .site-header[data-reveal] {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--color-bg);
  transition: transform var(--motion-duration) var(--motion-ease);

  &[data-hidden]:not(:has(:focus-visible)) { transform: translateY(-100%); }
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}

.site-header__name {
  font-weight: 700;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
}

.site-header__links {
  display: flex;
  gap: var(--space-4);
  padding: 0;
  list-style: none;

  a {
    font-size: 0.9375rem;
    color: var(--color-text-muted);
    text-decoration: none;

    @media (hover: hover) {
      &:hover { color: var(--color-text); }
    }

    &[aria-current] {
      color: var(--color-text);
      text-decoration: underline;
      text-decoration-color: var(--color-accent);
      text-underline-offset: 0.35em;
    }
  }
}

}
@layer components {
/* Section head: a strong rule, a small mono label and an optional note on the right. */
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-rule-strong);
}

/* Where an anchor landed: a redline draws along the section's rule once the scroll has settled, then goes.
   With reduced motion the jump is instant, so the rule itself stays in the accent colour as the pointer.
   The second selector covers links to a heading's id, like the one on About. */
@media (prefers-reduced-motion: no-preference) {
  :is(.work, .site-footer):target .section-head,
  .section-head:has(:target) {
    position: relative;

    &::before {
      content: "";
      position: absolute;
      top: -2px;
      left: 0;
      right: 0;
      height: 3px;
      background: var(--color-accent);
      transform: scaleX(0);
      transform-origin: left;
      animation: section-arrive 1400ms var(--ease-out) 700ms;
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  :is(.work, .site-footer):target .section-head,
  .section-head:has(:target) { border-top-color: var(--color-accent); }
}

@keyframes section-arrive {
  0% { transform: scaleX(0); opacity: 1; }
  35%, 70% { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}

.section-head__title {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.section-head__note {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  line-height: 1;
  color: var(--color-text-muted);
}

}
@layer components {
/* Button: a link that looks like an action. Variants: primary, secondary, external. */
.button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--color-text);
  font-weight: 600;
  text-decoration: none;

  &:active { transform: translateY(1px); }

  &[data-variant="primary"] {
    background: var(--color-text);
    color: var(--color-bg);

    @media (hover: hover) {
      &:hover {
        background: var(--color-accent);
        border-color: var(--color-accent);
        color: var(--color-on-accent);
      }
    }
  }

  @media (hover: hover) {
    &[data-variant="secondary"]:hover { border-color: var(--color-accent); }
  }

  &[data-variant="external"] {
    padding: 0 0 2px;
    border: 0;
    border-bottom: 1px solid var(--color-accent);
  }
}

}
@layer components {
/* Hero: name, role, one redline, lede and tagline over a faint layout grid.
   Everything above the draw-in block at the bottom is the end state. Without motion, the marks are simply there. */
.hero__inner {
  position: relative;
  padding-block: var(--space-8);

  > :not(.hero__grid) { position: relative; }

  @media (min-width: 900px) {
    padding-block: var(--space-16) 72px;
  }
}

.hero__grid {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-4);
  pointer-events: none;
  mask-image: linear-gradient(to bottom, #000 55%, transparent);

  span { background: var(--color-grid); }
  span:nth-child(n + 5) { display: none; }

  @media (min-width: 900px) {
    grid-template-columns: repeat(12, 1fr);
    gap: var(--space-6);
    span:nth-child(n + 5) { display: block; }
  }
}

.hero__name {
  font-size: var(--step-5);
  font-weight: 800;
  line-height: 0.92;
  letter-spacing: -0.04em;
}

.hero__role {
  margin-top: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* The element is the gap it measures. */
.hero__measure {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--space-8);
  padding-left: var(--space-1);

  &::before {
    content: "";
    align-self: stretch;
    width: 9px;
    border-block: 1px solid var(--color-accent);
    background: linear-gradient(var(--color-accent), var(--color-accent)) center / 1px 100% no-repeat;
  }

  span {
    padding: 3px 5px;
    border-radius: var(--radius-1);
    background: var(--color-accent);
    color: var(--color-on-accent);
    font-family: var(--font-mono);
    font-size: var(--step--2);
    font-weight: 500;
    line-height: 1;
  }
}

.hero__lede {
  max-width: 24ch;
  font-size: var(--step-3);
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.hero__tagline {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
  font-weight: 700;
  font-style: italic;

  /* The full sentence must stay on one line, or the page jumps when it closes up. So phones keep the smaller
     size, and on the narrowest ones the rule gives up its width and takes it back as the words close. */
  @media (min-width: 720px) { font-size: var(--step-1); }

  > span {
    flex: none;
    white-space: nowrap;
  }

  &::before {
    content: "";
    flex: 0 1 var(--space-6);
    height: 1px;
    background: var(--color-accent);
  }
}

/* Words the tagline edits away. Closed unless the animation below plays them. */
.hero__cut {
  position: relative;
  display: inline-block;
  max-width: 0;
  overflow: hidden;
  white-space: pre;
  vertical-align: bottom;

  &::after {
    content: "";
    position: absolute;
    top: 55%;
    left: 0;
    width: calc(100% - 0.25em);
    height: 2px;
    background: var(--color-accent);
    transform-origin: left;
  }

  &:nth-of-type(1) { --cut-width: 3em; --strike-at: 2400ms; --close-at: 4400ms; }
  &:nth-of-type(2) { --cut-width: 6.5em; --strike-at: 3000ms; --close-at: 5000ms; }
}

/* Draw-in: runs on load. The marks take under 1 s and the edit about 5.5 s. Only `from` is set, so each animation ends on the styles above.
   The redline draws first, then its label appears, while the tagline rule draws across. */
@media (prefers-reduced-motion: no-preference) {
  .hero__measure::before {
    --draw-from: inset(0 0 100% 0);

    animation: redline-draw var(--motion-duration) var(--motion-ease) calc(var(--motion-duration) / 3) backwards;
  }

  .hero__measure span {
    animation: redline-label var(--motion-duration) var(--motion-ease) var(--motion-duration) backwards;
  }

  /* The edit runs in the order an editor works: strike, strike, delete, delete. The waits are the point. People read the name
     and lede first, so the full sentence stands until they arrive, and the struck words stay long enough to be seen. */
  .hero__cut {
    animation: hero-cut-close 500ms var(--ease-in-out) var(--close-at) backwards;

    &::after { animation: hero-cut-strike 250ms var(--ease-out) var(--strike-at) backwards; }
  }

  .hero__tagline::before {
    --draw-from: inset(0 100% 0 0);

    animation: redline-draw var(--motion-draw-duration) var(--motion-ease) var(--motion-duration) backwards;
  }
}

@keyframes redline-draw {
  from { clip-path: var(--draw-from); }
}

@keyframes hero-cut-strike {
  from { transform: scaleX(0); }
}

@keyframes hero-cut-close {
  from { max-width: var(--cut-width); }
}

@keyframes redline-label {
  from { opacity: 0; }
}

}
@layer components {
/* Facts: a spec table of short label and value rows. Used by Now on Home and Background on About. */
.facts {
  display: grid;
  grid-template-columns: 4.25rem minmax(0, 1fr);
  gap: var(--space-2) var(--space-3);
  max-width: 44rem;

  dt {
    font-family: var(--font-mono);
    font-size: var(--step--2);
    line-height: 2;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
  }

  dd { margin: 0; }

  @media (min-width: 900px) {
    grid-template-columns: 7rem minmax(0, 1fr);
  }
}

}
@layer components {
/* Now: a spec table, then the walkthrough link. */
.now { padding-bottom: var(--space-10); }

.now .facts { margin-bottom: var(--space-5); }

}
@layer components {
/* Work section and case list: one column on phones, two on desktop. */
.work { padding-bottom: var(--space-10); }

.case-list {
  display: grid;
  gap: var(--space-6);
  padding: 0;
  list-style: none;

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

}
@layer components {
/* Case card: cover on the plate, then spec row, title, summary and role. The whole card is one link.
   Hover and keyboard focus share one state: --card-active goes from 0 to 1. Phones (no hover) never change. */
.case-card {
  --card-active: 0;

  position: relative;
  display: grid;
  grid-template-rows: auto 1fr;
  height: 100%;
  background: var(--color-surface);
  border: var(--card-border-width) solid var(--color-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  container-type: inline-size;

  &:has(a:focus-visible) {
    --card-active: 1;
    outline: var(--focus-ring);
    outline-offset: 3px;
  }

  @media (hover: hover) {
    &:hover { --card-active: 1; }
  }

  &:active { transform: translateY(1px); }
}

.case-card__body {
  display: grid;
  align-content: start;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--card-padding);
}

.case-card__media {
  order: -1;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--color-plate);
  border-bottom: var(--card-border-width) solid var(--color-border);

  img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

.case-card__meta {
  order: -1;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin-bottom: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  line-height: 1.4;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);

  span + span::before {
    content: "/ ";
    color: var(--color-border);
  }
}

.case-card__title {
  font-size: var(--step-2);
  font-weight: 700;

  a {
    text-decoration: none;

    /* Stretched link: the whole card is the target. */
    &::after {
      content: "";
      position: absolute;
      inset: 0;
      z-index: 1;
    }

    &:focus-visible { outline: none; }
    &:hover { color: inherit; }
  }

  .icon {
    display: inline-block;
    color: var(--color-accent);
    transform: translate(calc(2px * var(--card-active)), calc(-2px * var(--card-active)));
    transition: transform var(--motion-duration) var(--motion-ease);
  }
}

.case-card__summary {
  max-width: 46ch;
  color: var(--color-text-muted);
}

.case-card__role {
  margin-top: var(--space-1);
  font-size: 0.9375rem;
  font-weight: 600;
}

/* The card's padding shown as a hatched redline, with its real value. It only covers the padding, never the text. */
.case-card__deco {
  position: absolute;
  inset: calc(75cqi + var(--card-border-width)) 0 0;
  z-index: 2;
  pointer-events: none;
  opacity: var(--card-active);
  transition: opacity var(--motion-duration) var(--motion-ease);

  &::before {
    content: "";
    position: absolute;
    inset: 0;
    padding: var(--card-padding);
    background: repeating-linear-gradient(-45deg, var(--color-accent) 0 1px, transparent 1px 6px);
    opacity: 0.25;
    mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  }

  &::after {
    content: "";
    position: absolute;
    inset: var(--card-padding);
    outline: 1px dashed var(--color-accent);
  }
}

.case-card__label {
  position: absolute;
  top: 3px;
  left: 3px;
  padding: 2px 4px;
  border-radius: var(--radius-1);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 500;
  line-height: 1;
}

}
@layer components {
/* Contact: three links, stacked on phones, in a row on desktop. */
.contact__links {
  display: grid;
  padding: 0;
  list-style: none;

  a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding-block: var(--space-3);
    font-size: var(--step-2);
    font-weight: 600;
    letter-spacing: -0.015em;
    text-decoration: none;
  }

  li + li { border-top: 1px solid var(--color-border); }

  @media (min-width: 720px) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-6);
    li + li { border-top: 0; }
    a { justify-content: flex-start; }
  }
}

}
@layer components {
/* Site footer: the contact block, then one quiet line. */
.site-footer {
  padding-block: 0 var(--space-16); /* Clears the fixed theme switch at the end of the page. */
}

.site-footer__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-6);
  margin-top: var(--space-8);
}

.site-footer__note {
  font-size: var(--step--1);
  color: var(--color-text-muted);
}

.site-footer__privacy {
  margin-left: auto; /* Stays on the right when there's no note. */
  font-size: var(--step--1);
  color: var(--color-text-muted);

  @media (hover: hover) {
    &:hover { color: var(--color-text); }
  }

  &[aria-current] {
    color: var(--color-text);
    text-decoration-color: var(--color-accent);
  }
}

}
@layer components {
/* Theme switch: one row of radios, fixed to the same corner on every screen. `body >` skips the styleguide sample. */
.theme-switch {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

body > aside > .theme-switch {
  position: fixed;
  right: max(var(--space-2), env(safe-area-inset-right, 0px));
  bottom: max(var(--space-2), env(safe-area-inset-bottom, 0px));
  z-index: 5;
}

.theme-switch__options {
  display: flex;
  gap: 2px;
  width: fit-content;
  padding: 2px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  font-size: var(--step--1);
  line-height: 1;
}

/* Plain radio buttons: everyone knows what they do, and the whole label is the target. */
.theme-switch__option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.25rem;
  padding-inline: var(--space-2) var(--space-3);
  color: var(--color-text);
  cursor: pointer;

  @media (hover: hover) {
    &:hover { background: var(--color-bg); }
  }

  &:has(:focus-visible) {
    outline: var(--focus-ring);
    outline-offset: 2px;
  }

  input {
    flex: none;
    width: 1rem;
    height: 1rem;
    margin: 0;
    cursor: pointer;
    background: none;
    border: 1.5px solid var(--color-text);
    border-radius: 50%;
    appearance: none;

    &:checked {
      background: radial-gradient(circle, var(--color-accent) 50%, transparent 56%);
      border-color: var(--color-accent);
    }
  }

  @media (pointer: coarse) { min-height: 2.75rem; }
}

/* "Show me how" ends here: one tilt and three rings. theme-sandbox.js sets the attribute. */
@media (prefers-reduced-motion: no-preference) {
  body > aside > .theme-switch[data-attention] .theme-switch__options {
    animation:
      theme-tilt 600ms var(--ease-in-out),
      theme-pulse 750ms var(--ease-out) 3;
  }
}

@media (prefers-reduced-motion: reduce) {
  body > aside > .theme-switch[data-attention] .theme-switch__options {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
  }
}

@keyframes theme-pulse {
  from { box-shadow: 0 0 0 0 var(--color-accent); }
  to { box-shadow: 0 0 0 16px transparent; }
}

@keyframes theme-tilt {
  25% { transform: rotate(-4deg); }
  60% { transform: rotate(3deg); }
}

}
@layer components {
/* Theme sandbox: copy on one side, a framed preview with its own control on the other. */
.theme-sandbox { padding-bottom: var(--space-16); }

.theme-sandbox__layout {
  display: grid;
  gap: var(--space-5);

  @media (min-width: 900px) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-8);
  }
}

.theme-sandbox__copy {
  display: grid;
  align-content: start;
  justify-items: start;
  gap: var(--space-4);
}

/* A real button, reset to look like .button. :where keeps this weak enough for the variant and its hover to win. */
:where(.theme-sandbox__copy) .button {
  font: inherit;
  font-weight: 600;
  color: inherit;
  cursor: pointer;
  background: none;

  &:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
  }
}

.theme-sandbox__intro { font-size: var(--step-1); line-height: 1.35; }

/* Hidden until the sequence reaches the switch. It keeps its space, so nothing moves when it appears. */
.theme-sandbox__hint {
  color: var(--color-text-muted);

  &[hidden] {
    display: block;
    visibility: hidden;
  }
}

/* Points out the button: a redline runs round it once when it comes into view, then goes.
   The sequence itself starts with a redline from this button. theme-sandbox.js sets the attribute. */
@property --trace-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@media (prefers-reduced-motion: no-preference) {
  [data-show-me][data-trace] {
    position: relative;

    &::after {
      content: "";
      position: absolute;
      inset: -5px;
      pointer-events: none;
      border: 2px solid var(--color-accent);
      mask: conic-gradient(#000 var(--trace-angle), transparent 0);
      animation: button-trace 1200ms var(--ease-in-out) 300ms both;
    }
  }
}

@keyframes button-trace {
  0% { --trace-angle: 0deg; opacity: 1; }
  70% { --trace-angle: 360deg; opacity: 1; }
  100% { --trace-angle: 360deg; opacity: 0; }
}

/* The frame: dashed, so it reads as a bounded area and not as part of the page. */
.theme-sandbox__stage {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-3);
  border: 1px dashed var(--color-text-muted);
  transition: border-color var(--motion-duration) var(--motion-ease);

  &[data-active] { border: 1px solid var(--color-accent); }

  iframe {
    width: 100%;
    height: 19rem;
    background: none;
    border: 0;

    @media (min-width: 900px) { height: 13rem; }
  }
}

.theme-sandbox__label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--color-text-muted);

  span { display: inline-block; }

  [aria-current] {
    color: var(--color-text);
    font-weight: 700;
    text-decoration: underline;
    text-decoration-color: var(--color-accent);
    text-underline-offset: 0.3em;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .theme-sandbox__label [data-pulse] {
    animation:
      theme-label-pop 350ms var(--ease-out),
      theme-pulse 650ms var(--ease-out);
  }
}

@keyframes theme-label-pop {
  40% { transform: scale(1.25); }
}

/* The redlines that lead from the button to the sandbox, then to the real switch. theme-sandbox.js draws them. */
.theme-sandbox__line {
  position: fixed;
  inset: 0;
  z-index: 4;
  width: 100%;
  height: 100%;
  pointer-events: none;

  path {
    fill: none;
    stroke: var(--color-accent);
    stroke-width: 2.5px;
  }
}

/* The page inside the iframe: a few real components, so each theme shows what it does to them. */
.theme-demo {
  display: grid;
  align-content: center;
  gap: var(--space-3);
  min-height: 100svh;
  padding: var(--space-3);
  overflow: hidden;

  @media (min-width: 26rem) {
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    align-items: center;
    gap: var(--space-5);
  }

  .prose { gap: var(--space-3); }
  .prose h2 { margin-top: 0; }
  .quote { font-size: var(--step-0); }
}

.theme-demo__facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  padding: 0;
  list-style: none;

  @media (min-width: 26rem) { grid-template-columns: minmax(0, 1fr); }

  .stat { margin: 0; }
  .stat__value { font-size: var(--step-3); }
  .stat__label { font-size: var(--step--2); }
}

}
@layer components {
/* Page frame and header for plain pages (About, Privacy, 404). The optional picture sits beside the text.
   .page--rail gives the page the same heading rail as a case page (see prose.css).
   On a rail page the picture is a cut-out: see the end of this file. */
.page { padding-block: var(--space-8) var(--space-24); }

.page-header {
  display: grid;
  gap: var(--space-5);
  margin-bottom: var(--space-12);

  &:has(.page-header__media) {
    grid-template-columns: minmax(0, 1fr) clamp(7.5rem, 30vw, 18rem);
    grid-template-rows: auto 1fr;

    @media (min-width: 900px) {
      grid-template-columns: minmax(0, var(--measure)) 18rem;
      justify-content: space-between;
    }
  }
}

.page-header__label {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.page-header__title {
  max-width: var(--measure);
  font-size: var(--step-4);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.035em;
}

.page-header__summary {
  max-width: var(--measure);
  font-size: var(--step-1);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.page-header__media {
  grid-column: 2;
  grid-row: 1 / span 2;
  max-width: 18rem;
}

/* Rail page with a picture: a cut-out leans in from the browser edge and stands on the line
   that opens the first section. Its right and bottom edges are hard cuts, so they only work there.
   That line runs out to the browser edge, and replaces the first section's own rule. */
main:has(.page--rail) { overflow-x: clip; } /* 100vw counts the scrollbar. */

.page--rail > .page-header:has(.page-header__media) {
  /* Grows again past 1400px, so the picture doesn't feel far from the text on wide screens. */
  --lean-height: clamp(12.5rem, min(30vw, 21rem + max(0px, 100vw - 87.5rem) * 0.154), 26rem);
  --lean-width: calc(var(--lean-height) * 1200 / 1870);
  --bleed: calc((100vw - 100%) / 2);

  position: relative;
  display: flex;
  flex-direction: column;
  min-height: var(--lean-height);
  margin-bottom: 0;
  padding-bottom: var(--space-8);
  padding-right: max(0px, calc(var(--lean-width) + var(--space-4) - var(--bleed)));

  &::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 0;
    right: calc(-1 * var(--bleed));
    border-top: 1px solid var(--color-rule-strong);
  }

  .page-header__media {
    position: absolute;
    grid-area: auto; /* Otherwise the old grid cell becomes its frame. */
    right: calc(-1 * var(--bleed));
    bottom: 0;
    width: var(--lean-width);
    max-width: none;
  }

  + .prose {
    @media (max-width: 899px) { padding-top: var(--space-5); }

    > h2:first-child {
      margin-top: 0;

      &::before { content: none; }
    }
    > h2:first-child + * { margin-top: var(--rule-gap, 0); }
  }
}

/* The empty space under the opening line, measured like a browser's inspect overlay.
   Its height is the token it labels. Desktop only: on phones there is no gap, and it never sits behind text. */
.page-header__measure { display: none; }

@media (min-width: 900px) {
  .page--rail > .page-header:has(.page-header__measure) { padding-bottom: 0; }

  .page-header__measure {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    height: var(--space-24);
    margin-top: auto;
    padding-right: var(--space-8);

    /* The hatch stops at the bracket. */
    &::before {
      content: "";
      position: absolute;
      inset: 0 var(--space-8) 0 0;
      outline: 1px dashed var(--color-accent);
      outline-offset: -1px;
      background: repeating-linear-gradient(-45deg, var(--color-accent) 0 1px, transparent 1px 6px);
      opacity: 0.3;
    }

    /* The bracket: same drawing as the measure in the hero. */
    &::after {
      content: "";
      position: absolute;
      top: 0;
      bottom: 0;
      right: var(--space-5);
      width: 9px;
      border-block: 1px solid var(--color-accent);
      background: linear-gradient(var(--color-accent), var(--color-accent)) center / 1px 100% no-repeat;
    }
  }

  .page-header__code {
    position: relative;
    display: grid;
    gap: 2px;
    margin-left: var(--space-3);
    padding: var(--space-1) var(--space-2);
    background: var(--color-bg);
    font-family: var(--font-mono);
    font-size: var(--step--2);
    line-height: 1.4;
    color: var(--color-text-muted);

    [data-inspect="selector"] { color: var(--color-accent); }
  }

  .page-header__value {
    position: absolute;
    right: 0;
    font-family: var(--font-mono);
    font-size: var(--step--2);
    line-height: 1;
    color: var(--color-accent);
  }

  /* What the readout is describing, while the pointer is on it. */
  .page--rail [data-inspected] {
    outline: 1px dashed var(--color-accent);
    outline-offset: 3px;
  }
}

/* Same order as the hero: the gap shows, the bracket draws across it, then its value and the readout appear. */
@media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
  .page-header__measure::before {
    animation: redline-label var(--motion-duration) var(--motion-ease) backwards;
  }

  .page-header__measure::after {
    --draw-from: inset(0 0 100% 0);

    animation: redline-draw var(--motion-duration) var(--motion-ease) calc(var(--motion-duration) / 3) backwards;
  }

  .page-header__code,
  .page-header__value {
    animation: redline-label var(--motion-duration) var(--motion-ease) var(--motion-duration) backwards;
  }
}

}
@layer components {
/* Case page frame and header. */
.case { padding-block: var(--space-8) var(--space-24); }

.case-header {
  display: grid;
  gap: var(--space-5);
  max-width: var(--measure);
  margin-bottom: var(--space-12);
}

/* Desktop: text and facts on the left, cover on the right, at the width of a card on Home.
   Only on the real page: styleguide panels are too narrow for two columns. */
@media (min-width: 900px) {
  .case > .case-header {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: auto auto auto 1fr;
    column-gap: var(--space-6);
    max-width: none;

    > :not(.case-header__media) { grid-column: 1; }

    .case-header__media {
      grid-column: 2;
      grid-row: 1 / -1;
      align-self: start;
    }

    > .case-header__stats { grid-column: 1 / -1; }
  }
}

.case-header__back {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);

  a {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    text-decoration: none;
  }
}

.case-header__title {
  font-size: var(--step-4);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.035em;
}

.case-header__summary {
  font-size: var(--step-1);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

/* Cover on the plate, cropped to 4:3. */
.case-header__media {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--color-plate);

  img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

/* Spec table: every row is optional except client and year. */
.case-header__facts {
  display: grid;
  grid-template-columns: 4.25rem minmax(0, 1fr);
  gap: var(--space-2) var(--space-3);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-rule-strong);

  dt {
    font-family: var(--font-mono);
    font-size: var(--step--2);
    line-height: 2;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
  }

  dd { margin: 0; }

  @media (min-width: 900px) {
    grid-template-columns: 7rem minmax(0, 1fr);
  }
}

.case-header__outcomes,
.case-header__links {
  display: grid;
  gap: var(--space-1);
  padding: 0;
  list-style: none;
}

/* Big numbers: one row on desktop, two by two on phones. */
.case-header__stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--space-5) var(--space-6);
  padding: 0;
  list-style: none;

  .stat {
    display: grid;
    margin: 0;
  }

  .stat__value { font-size: var(--step-3); }
  .stat__label { font-size: var(--step--2); }
}

}
@layer components {
/* Next case: closes a case page, so it doesn't end in the footer. Same strong rule as a section head. */
.next-case {
  margin-top: var(--space-16);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-rule-strong);

  a {
    display: grid;
    gap: var(--space-2);
    width: fit-content;
    text-decoration: none;

    @media (hover: hover) {
      &:hover { color: inherit; }
      &:hover .icon { transform: translateX(4px); }
    }

    &:focus-visible .icon { transform: translateX(4px); }
    &:active { transform: translateY(1px); }
  }

  .icon {
    display: inline-block;
    color: var(--color-accent);
    transition: transform var(--motion-duration) var(--motion-ease);
  }
}

.next-case__label {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.next-case__title {
  font-size: var(--step-3);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

}
@layer components {
/* Prose: rendered Markdown body. Text stays at the reading measure; figures and galleries use the full width. */
.prose {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
  overflow-wrap: anywhere;

  > * { max-width: var(--measure); }
  > :is(.figure, .gallery, .walkthrough) {
    max-width: none;
    margin-block: var(--space-4);
  }
  > .figure[data-size="small"] {
    @media (min-width: 46rem) { max-width: 70%; }
  }
  > :first-child { margin-top: 0; }
  > .button { justify-self: start; }

  h2 {
    margin-top: var(--space-10);
    font-size: var(--step-3);
    font-weight: 700;
  }
  h3 {
    margin-top: var(--space-5);
    font-size: var(--step-1);
    font-weight: 700;
  }
  :is(ul, ol) { padding-left: 1.25em; }
  li + li { margin-top: var(--space-1); }
}

/* Case pages and rail pages, desktop: a rail for the headings, one column for everything else.
   Each heading's rule runs across both, so a section opens the way it does on Home. */
@media (min-width: 900px) {
  :is(.case, .page--rail) > .prose {
    --rail-gap: var(--space-6);
    --section-gap: var(--space-16);
    --subsection-gap: var(--space-8);
    --rule-gap: var(--space-4);

    container-type: inline-size;
    grid-template-columns: 23.3% minmax(0, 1fr);
    column-gap: var(--rail-gap);
    font-size: var(--prose-size);

    > * { grid-column: 2; }

    > :is(h2, h3) {
      position: relative;
      grid-column: 1;
      padding-top: var(--rule-gap);

      &::before {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        width: 100cqw;
        border-top: 1px solid var(--color-rule-strong);
      }
    }

    > h2 {
      margin-top: var(--section-gap);
      font-size: var(--step-2);
    }

    > h3 {
      margin-top: var(--subsection-gap);
      font-family: var(--font-mono);
      font-size: var(--step--1);
      font-weight: 500;
      line-height: 1.5;
      letter-spacing: 0.08em;
      text-transform: uppercase;

      &::before { border-color: var(--color-border); }
    }

    /* The block beside a heading starts level with it, under the rule. */
    > h2 + * { margin-top: calc(var(--section-gap) + var(--rule-gap)); }
    > h3 + * { margin-top: calc(var(--subsection-gap) + var(--rule-gap)); }
  }
}

}
@layer components {
/* Figure: image on the plate + caption. Clean's plate is transparent; a theme can give it a colour.
   Small images keep their own size, centred. Very tall ones are capped to the screen.
   data-size="small": a transparent illustration, not evidence. Cropped to trim its empty margin. Prose sets its width. */
.figure {
  display: grid;
  gap: var(--space-2);
  min-width: 0;

  picture {
    display: flex;
    justify-content: center;
    background: var(--color-plate);
  }

  img {
    max-height: 85svh;
    object-fit: contain;
  }
}

.figure[data-size="small"] {
  picture {
    justify-content: flex-start;
    background: none;
  }

  img {
    width: 100%;
    aspect-ratio: 2 / 1;
    object-fit: cover;
  }
}

/* An SVG placed in the page (see inlineSvg in eleventy.config.js). */
.figure__svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 85svh;
}

/* The caption, with an optional link on the right. */
.figure__caption {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-1) var(--space-4);
  font-size: var(--step--1);
  color: var(--color-text-muted);

  /* Markdown can't use the icon shortcode, so the external-link icon comes from a mask. */
  a {
    color: var(--color-text);

    @media (hover: hover) {
      &:hover { color: var(--color-accent); }
    }

    &::after {
      content: "";
      display: inline-block;
      width: 1em;
      height: 1em;
      margin-left: 0.3em;
      vertical-align: -0.15em;
      background: currentColor;
      mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 3h6v6'/%3E%3Cpath d='M10 14 21 3'/%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3C/svg%3E") center / contain no-repeat;
    }
  }
}

}
@layer components {
/* Gallery: a grid of figures. One column on phones, two on desktop, three for a set of three.
   data-row: stays two in a row on phones, for small illustrations. */
.gallery {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);

  img { width: 100%; }

  &[data-row] { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  @media (min-width: 720px) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    &:has(> :nth-child(3):last-child) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  }
}

}
@layer components {
/* Persona: a portrait beside a short text. No plate, and no crop: the picture brings its own shape. */
.persona {
  display: grid;
  grid-template-columns: 8rem minmax(0, 1fr);
  gap: var(--space-4);
  align-items: center;

  .figure picture { background: none; }
  img { width: 100%; }

  @media (min-width: 720px) {
    grid-template-columns: 14rem minmax(0, 1fr);
    gap: var(--space-6);
  }
}

}
@layer components {
/* Stat: one highlighted number with a mono label. */
.stat {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin-block: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-rule-strong);
}

.stat__value {
  font-family: var(--font-display);
  font-size: var(--step-4);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.035em;
}

.stat__label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

}
@layer components {
/* Quote: blockquote.quote. In Markdown the class comes from a rule in eleventy.config.js. */
.quote {
  display: grid;
  gap: var(--space-2);
  padding-left: var(--space-4);
  border-left: 2px solid var(--color-accent);
  font-size: var(--step-1);
  line-height: 1.4;
}

.quote__source {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

}
@layer components {
/* Callout: an aside. Becomes a speech bubble in Comic. */
.callout {
  position: relative;
  display: grid;
  gap: var(--space-2);
  padding: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
}

.callout__label {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

}
@layer components {
/* Flow: a short decision flow, built as an ordered list. On phones the outcomes sit under each question.
   From 46rem the outcomes move to a second column, joined by real lines. Lines and arrowheads use tokens only. */
.flow {
  --flow-arm: 6rem;
  --flow-drop: var(--space-10);
  --flow-spine: var(--space-4);
  --flow-head: 0.4rem;
  --flow-line: 1px solid var(--color-text-muted);

  display: grid;
  gap: var(--space-3);
  margin-block: var(--space-4);
}

.flow .flow__list {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.flow .flow__step {
  position: relative;
  display: grid;
  margin: 0;

  /* The line running down from one question to the next. */
  &::before {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-start: var(--flow-spine);
    border-inline-start: var(--flow-line);
  }
}

.flow__box {
  position: relative;
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}

.flow__box--end {
  border-color: var(--color-rule-strong);
  font-weight: 700;
}

.flow__branch {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding-block-start: var(--space-3);
  padding-inline-start: calc(var(--flow-spine) + var(--space-3));

  .flow__box { flex: 1; }
}

.flow__label,
.flow__down {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* The arrow out of a question. */
.flow__label {
  position: relative;
  padding-inline-end: var(--space-6);

  &::before {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-end: 0;
    width: var(--space-5);
    border-block-start: var(--flow-line);
  }

  &::after {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-end: 0;
    width: var(--flow-head);
    height: var(--flow-head);
    border-block-start: var(--flow-line);
    border-inline-end: var(--flow-line);
    transform: translateY(-50%) rotate(45deg);
    transform-origin: center;
  }
}

/* The way on to the next question, with its arrowhead. */
.flow__down {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--flow-drop);
  padding-inline-start: calc(var(--flow-spine) + var(--space-3));

  &::after {
    content: "";
    position: absolute;
    inset-block-end: 1px;
    inset-inline-start: var(--flow-spine);
    width: var(--flow-head);
    height: var(--flow-head);
    border-block-end: var(--flow-line);
    border-inline-end: var(--flow-line);
    transform: translateX(-50%) rotate(45deg);
  }
}

.flow .flow__step--last::before { content: none; }

.flow__caption {
  max-width: var(--measure);
  font-size: var(--step--1);
  color: var(--color-text-muted);
}

@media (min-width: 46rem) {
  .flow { --flow-spine: 50%; }

  .flow .flow__step {
    grid-template-columns: minmax(0, 1fr) var(--flow-arm) minmax(0, 1fr);

    &::before { inset-inline-start: calc((100% - var(--flow-arm)) / 4); }
  }

  .flow .flow__step > .flow__box,
  .flow__down { grid-column: 1; }

  .flow__branch { display: contents; }

  .flow__branch .flow__box { grid-column: 3; }

  /* The label sits on top of the line between the two columns. */
  .flow__label {
    grid-column: 2;
    align-self: center;
    padding: 0 0 var(--space-1);
    text-align: center;
    transform: translateY(-50%);

    &::before {
      inset-block: auto 0;
      inset-inline: 0;
      width: auto;
    }

    &::after {
      inset-block: auto 0;
      transform: translateY(50%) rotate(45deg);
    }
  }

  .flow__down { padding-inline-start: calc(50% + var(--space-3)); }

  /* Second use: right from the fallback, then up into the outcome above it. */
  .flow .flow__step--last {
    &::before {
      content: "";
      inset-block: calc(-1 * var(--flow-drop)) auto;
      inset-inline: auto calc((100% - var(--flow-arm)) / 4);
      width: var(--flow-head);
      height: var(--flow-head);
      border-block-start: var(--flow-line);
      border-inline: 0;
      border-inline-start: var(--flow-line);
      transform: translate(50%, 1px) rotate(45deg);
    }

    &::after {
      content: "";
      position: absolute;
      inset-block: calc(-1 * var(--flow-drop)) 50%;
      inset-inline: calc((100% - var(--flow-arm)) / 2) calc((100% - var(--flow-arm)) / 4);
      border-block-end: var(--flow-line);
      border-inline-end: var(--flow-line);
    }

    .flow__label {
      &::before,
      &::after { content: none; }
    }
  }

  .flow .flow__target {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

}
@layer components {
/* Walkthrough: one screen at a time, chosen from a list of steps. The script sets data-ready.
   Without it the figures stack with their captions. Numbers on small screens, a named list in the rail. */
.walkthrough {
  display: grid;
  gap: var(--space-6);
  min-width: 0;

  &[data-ready] { gap: var(--space-3); }

}

/* Screens share one grid cell. Hidden ones keep their box, so their images load early and switching doesn't flash. */
.walkthrough__screen {
  grid-row: 2;
  grid-column: 1;
  min-width: 0;

  &[hidden] {
    display: block;
    visibility: hidden;
  }

  img { cursor: pointer; }
}

.walkthrough__steps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.walkthrough__step {
  flex: 1 1 0;
  max-width: 3rem;
  min-height: 2.25rem;
  padding: 0;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--color-text-muted);
  cursor: pointer;
  background: none;
  border: 1px solid var(--color-border);

  @media (hover: hover) {
    &:hover { color: var(--color-text); }
  }

  &:active { transform: translateY(1px); }

  &[aria-selected="true"] {
    color: var(--color-text);
    border-color: var(--color-accent);
  }
}

.walkthrough__tip { display: none; }

.walkthrough__label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Case pages, desktop: the steps go in the rail, the screen in the column beside it. */
@media (min-width: 900px) {
  .case > .prose > .walkthrough[data-ready] {
    grid-column: 1 / -1;
    grid-template-columns: 23.3% minmax(0, 1fr);
    column-gap: var(--space-6);

    .walkthrough__screen {
      grid-row: 1;
      grid-column: 2;
    }

    .figure__caption { display: none; }

    .walkthrough__steps {
      position: relative;
      flex-direction: column;
      flex-wrap: nowrap;
      gap: 0;
    }

    .walkthrough__tip {
      display: block;
      margin-top: var(--space-4);
      font-family: var(--font-mono);
      font-size: var(--step--2);
      line-height: 1.5;
      color: var(--color-text-muted);
    }

    .walkthrough__step {
      flex: none;
      display: flex;
      gap: var(--space-2);
      max-width: none;
      padding: var(--space-2) var(--space-3);
      font-family: var(--font-body);
      text-align: left;
      border: 0;
      border-left: 2px solid var(--color-border);

      &[aria-selected="true"] { border-left-color: var(--color-accent); }
    }

    .walkthrough__number { font-family: var(--font-mono); }
    .walkthrough__label { all: unset; }
  }
}

/* Points out the steps: the accent line runs down them and back up, then the active step blinks twice. */
@media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
  .case > .prose > .walkthrough[data-draw] {
    .walkthrough__steps::before {
      content: "";
      position: absolute;
      inset: 0 auto 0 0;
      width: 3px;
      pointer-events: none;
      background: var(--color-accent);
      transform: scaleY(0);
      transform-origin: top;
      animation: walkthrough-draw 1200ms var(--ease-in-out);
    }

    [aria-selected="true"] {
      animation: walkthrough-blink 200ms linear 1200ms 2;
    }
  }
}

@keyframes walkthrough-draw {
  50% { transform: scaleY(1); }
}

@keyframes walkthrough-blink {
  50% { border-left-color: transparent; }
}

}
/* Page-to-page view transitions (PLAN §9). CSS only; browsers without support just navigate.
   The rule that turns them on, @view-transition, is inline in base.njk: see the note there.
   Not in a layer, and all of it is off under reduced motion.
   The cover's name is set per case in the markup (`morph`), because names must be unique on a page:
   a duplicate cancels the transition, so the styleguide's samples get none. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: var(--motion-fade-duration);
  }

  /* The header and theme switch stay put. `body >` skips the samples on styleguide pages. */
  body > .site-header { view-transition-name: site-header; }
  body > aside > .theme-switch { view-transition-name: theme-switch; }

  ::view-transition-group(site-header),
  ::view-transition-new(site-header),
  ::view-transition-group(theme-switch),
  ::view-transition-new(theme-switch) { animation: none; }

  ::view-transition-old(site-header),
  ::view-transition-old(theme-switch) { display: none; }

  /* Changing theme is one crossfade of the whole page. theme-switch.js sets the attribute. */
  [data-switching] * { view-transition-name: none !important; }

  /* Entrances mark the page's arrival, not a theme change, so they stop at the first switch. Themes use different
     animations here, and changing one restarts it. theme-switch.js sets data-settled. The hero's also stop once
     seen: the head script on Home sets data-hero-seen for visitors already on the site. */
  :is([data-settled], [data-hero-seen]) .hero__measure::before,
  :is([data-settled], [data-hero-seen]) .hero__measure span,
  :is([data-settled], [data-hero-seen]) .hero__tagline::before,
  :is([data-settled], [data-hero-seen]) .hero__cut,
  :is([data-settled], [data-hero-seen]) .hero__cut::after,
  [data-settled] .page-header__measure::before,
  [data-settled] .page-header__measure::after,
  [data-settled] .page-header__code,
  [data-settled] .page-header__value { animation: none; }

  /* The cover moves from the card to the case header, and back. */
  .case-card__media,
  .case-header__media { view-transition-class: case-cover; }

  /* Slower than the rest of the site's motion: this one has to be followed by eye. */
  ::view-transition-group(.case-cover) {
    animation-duration: var(--motion-morph-duration);
    animation-timing-function: var(--motion-ease);
  }

  /* The image from the page you left stays underneath for the whole move, so the cover can't flash empty
     if the new page's image hasn't loaded yet. The new cover fades in over it near the end, so a frame
     that only one page has (Comic's ink, Vector's border) arrives instead of snapping on. */
  ::view-transition-old(.case-cover),
  ::view-transition-new(.case-cover) { mix-blend-mode: normal; }

  ::view-transition-old(.case-cover) {
    animation: none;
    opacity: 1;
  }

  ::view-transition-new(.case-cover) {
    animation: cover-settle var(--motion-morph-duration) linear both;
  }

  /* A cover with no partner on the other page leaves with the page, so it can't linger. */
  ::view-transition-old(.case-cover):only-child {
    animation: cover-leave var(--motion-fade-duration) linear both;
  }

  ::view-transition-new(.case-cover):only-child {
    animation: cover-arrive var(--motion-fade-duration) linear both;
  }
}

@keyframes cover-settle {
  0%, 60% { opacity: 0; }
}

@keyframes cover-leave {
  to { opacity: 0; }
}

@keyframes cover-arrive {
  from { opacity: 0; }
}

