/* Fonts. Both files are served exactly as supplied: no conversion, no subsetting. */
@font-face {
  font-family: "NYXERIN";
  src: url("../fonts/NYXERIN.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
@font-face {
  font-family: "CamingoCode";
  src: url("../fonts/CamingoCode-Regular.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Three-tone dark system: base, raised panel, panel border. */
  --bg: #0a0a0a; /* flat neutral near-black base: one solid color, no gradients or washes */
  --panel: #1e2019; /* raised surfaces: case columns, logo cells */
  --border: #393e41; /* panel edges */
  --text: #b9d2b1; /* muted pale sage. 12.2:1 on base, 10.1:1 on panel: all text at full strength */
  --accent: #d64a02; /* burnt orange. 4.55:1 on base, 3.8:1 on panel: large text only (24px+) */
  --rule: rgb(185 210 177 / 0.45); /* lines only, never text */
  --mark: rgb(185 210 177 / 0.3);

  --font-display: "NYXERIN", "CamingoCode", ui-monospace, monospace;
  --font-mono: "CamingoCode", ui-monospace, "SF Mono", Menlo, monospace;

  --gutter: clamp(16px, 4vw, 56px);
  --max: 1440px;
  --section: clamp(96px, 16vw, 208px);

  --step--1: 0.8125rem;
  --step-0: 1.0625rem;
  --step-1: clamp(1.1875rem, 1.05rem + 0.5vw, 1.375rem);
  --step-2: clamp(1.5rem, 1.2rem + 1vw, 2rem);
  --display-l: clamp(2.5rem, 1.1rem + 6.4vw, 6rem);
  --display-m: clamp(2.25rem, 1.4rem + 4vw, 4.75rem);

  --hair: 1px solid var(--rule);
  --edge: 1px solid var(--border);

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --snap: steps(1, end);

  color-scheme: dark;
}

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

html {
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scrollbar-color: var(--rule) var(--bg);
  scrollbar-width: thin;
}

body {
  margin: 0;
  min-height: 100svh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--step-0);
  line-height: 1.7;
  caret-color: var(--text);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

::selection {
  background: var(--text);
  color: var(--bg);
}

::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: var(--bg);
}
::-webkit-scrollbar-thumb {
  background: var(--rule);
}

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

:focus-visible {
  outline: 1px solid var(--text);
  outline-offset: 4px;
}

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

h1,
h2,
h3,
p,
ul,
figure {
  margin: 0;
}

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

.hit {
  font-style: normal;
  color: var(--accent);
}

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

.skip {
  position: absolute;
  left: var(--gutter);
  top: 12px;
  z-index: 10;
  padding: 8px 12px;
  background: var(--bg);
  border: var(--hair);
  text-decoration: none;
  transform: translateY(-200%);
}
.skip:focus-visible {
  transform: none;
}

/* Meta labels: the // voice. */
.label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.5;
}
.dim {
  color: var(--text);
}

.icon {
  width: 1em;
  height: 1em;
  flex: none;
  stroke: currentColor;
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: square;
}

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

.hero {
  position: relative;
  height: 100svh;
  min-height: 560px;
  max-height: 1100px;
  overflow: hidden;
  isolation: isolate;
}

.topbar {
  position: absolute;
  inset: 0 0 auto;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 20px var(--gutter);
}
.topbar a {
  text-decoration: none;
}
.topbar a:hover {
  text-decoration: underline;
}
.topbar .id {
  font-family: var(--font-display);
  font-size: var(--step-1);
  letter-spacing: 0.02em;
}
.topbar nav {
  display: flex;
  gap: 28px;
}
.topbar .contact-full {
  display: none;
}

.stage {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.crt-canvas,
.crt-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.crt-canvas {
  display: none;
  opacity: 0;
  transition: opacity 700ms var(--ease-out);
}
.crt-canvas.is-live {
  opacity: 1;
}
/* The glitch burst's box outlines and labels, drawn over the CRT. */
.crt-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: none;
  pointer-events: none;
}
/* Phones (and desktop without WebGL): the head loop as a flat, dimmed,
   full-cover background behind the headline. */
.crt-video {
  object-fit: cover;
  background: var(--bg);
  opacity: 0.38;
}

/* The HUD frame: bracket corners and edge ticks, inset from the viewport. */
.frame {
  position: absolute;
  inset: 72px var(--gutter) 64px;
  z-index: 1;
  pointer-events: none;
}
.frame i {
  position: absolute;
  width: 20px;
  height: 20px;
  border: 0 solid var(--text);
}
.frame i:nth-child(1) { top: 0; left: 0; border-top-width: 1px; border-left-width: 1px; }
.frame i:nth-child(2) { top: 0; right: 0; border-top-width: 1px; border-right-width: 1px; }
.frame i:nth-child(3) { bottom: 0; left: 0; border-bottom-width: 1px; border-left-width: 1px; }
.frame i:nth-child(4) { bottom: 0; right: 0; border-bottom-width: 1px; border-right-width: 1px; }
.frame b {
  position: absolute;
  background: var(--mark);
}
.frame b:nth-of-type(1) { top: 50%; left: 0; width: 10px; height: 1px; }
.frame b:nth-of-type(2) { top: 50%; right: 0; width: 10px; height: 1px; }
.frame b:nth-of-type(3) { left: 50%; top: 0; width: 1px; height: 10px; }
.frame b:nth-of-type(4) { left: 50%; bottom: 0; width: 1px; height: 10px; }

.hero-line {
  position: absolute;
  z-index: 2;
  left: var(--gutter);
  right: var(--gutter);
  top: 50%;
  transform: translateY(-50%);
  padding-inline: clamp(12px, 3vw, 40px);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--display-l);
  line-height: 0.98;
  letter-spacing: -0.01em;
  text-align: center;
  text-wrap: balance;
}
.hero-line .st-main > span,
.hero-line .st-echo > span {
  display: block;
}

.hud {
  position: absolute;
  z-index: 2;
  bottom: 84px;
  color: var(--text);
}
.hud-bl {
  left: calc(var(--gutter) + 20px);
  right: calc(var(--gutter) + 20px);
}
.hud-bl p {
  white-space: nowrap;
}
.hud-br {
  top: 92px;
  bottom: auto;
  right: calc(var(--gutter) + 20px);
  max-width: 60%;
  text-align: left; /* label and brand share one left edge; min-width keeps the box still */
}
.cycle {
  font-size: 1.5rem;
  letter-spacing: 0.04em;
  line-height: 1.3;
}
.cycle-word {
  color: var(--accent);
  display: inline-block;
  min-width: 13ch;
  text-align: left;
}

/* Groups the HUD blocks in the markup only; each block positions itself. */
.hud-dock {
  display: contents;
}

@media (max-width: 719px) {
  /* Phone hero: one full screen (100svh, so the address bar never clips it)
     in three zones. Top, pinned: name bar, top brackets, taglines. Bottom,
     pinned: readout, bottom brackets, enter cue. Middle: the headline over
     the flat video, centered on the true middle of the screen and sized to
     --p, a square that keeps at least --g of clear space to whichever zone is
     taller and shrinks on short screens to keep it.
       --f   frame top: 28px of air under the name
       --fb  frame bottom: the same 28px of air above the enter cue, which
             sits as far from the screen's foot as the name does from its top
       --in  how far each HUD block sits in from its bracket's corner
       --top / --bot  heights of the top and bottom zones
       --g   minimum clear space between --p and either zone */
  .hero {
    --f: 68px;
    --fb: 71px;
    --in: 16px;
    --top: calc(var(--f) + var(--in) + 39px);
    --bot: calc(var(--fb) + var(--in) + 48px);
    --g: 20px;
    --p: min(
      calc(100vw - 2 * var(--gutter)),
      calc(100svh - 2 * (max(var(--top), var(--bot)) + var(--g)))
    );
    height: 100svh;
    min-height: 0;
    max-height: none;
    background: var(--bg);
  }
  @supports not (height: 100svh) {
    .hero {
      --p: min(
        calc(100vw - 2 * var(--gutter)),
        calc(100vh - 2 * (max(var(--top), var(--bot)) + var(--g)))
      );
      height: 100vh; /* only for browsers without svh */
    }
  }
  .topbar .id {
    font-size: clamp(1rem, 4.6vw, 1.1875rem);
    white-space: nowrap;
  }
  .frame {
    inset: var(--f) var(--gutter) var(--fb);
  }
  .hero .scroll-cue {
    bottom: 12px;
    padding: 12px 20px; /* 44px-tall tap target; glyphs stay 24px off the foot */
  }
  .hero-line {
    top: 50%; /* the true middle of the screen */
    font-size: clamp(1.125rem, min(9.5vw, calc(var(--p) * 0.12)), 2.5rem);
  }
  /* Taglines mirror the readout: in from the top-right bracket by the bracket
     arm (20px) horizontally and --in vertically, right-aligned. The third line
     is desktop-only. */
  .hud-bl {
    top: calc(var(--f) + var(--in));
    bottom: auto;
    left: auto;
    right: calc(var(--gutter) + 20px);
    text-align: right;
  }
  .hud-bl p:nth-child(3) {
    display: none;
  }
  /* Readout: in from the bottom-left bracket by the arm and --in. */
  .hud-br {
    top: auto;
    bottom: calc(var(--fb) + var(--in));
    left: calc(var(--gutter) + 20px);
    right: calc(var(--gutter) + 20px);
    max-width: none;
  }
  .cycle {
    font-size: 1.375rem; /* on the base, orange passes at this size (4.55:1) */
  }
  /* About a finger-width (~64px) from the enter cue's text to the cases label:
     the cue's 24px foot clearance plus this. */
  main .worlds {
    padding-top: 40px;
  }
}

.scroll-cue {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  color: var(--text);
}
.scroll-cue:hover {
  color: var(--text);
}

/* ───────────────────────── Sections ───────────────────────── */

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

.sec-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding-bottom: 14px;
  border-bottom: var(--hair);
  margin-bottom: clamp(24px, 4vw, 40px);
}

.worlds {
  padding-top: var(--section);
}

/* ─── Case chooser ─── */

.chooser {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}

.case {
  position: relative;
  padding-inline: 16px;
  background: var(--panel);
  border: var(--edge);
}
.case h3 {
  font: inherit;
}

.case-toggle {
  all: unset;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "num mark sign"
    ". tag tag";
  column-gap: 16px;
  row-gap: 10px;
  align-items: baseline;
  width: 100%;
  padding: 28px 0 26px;
  cursor: pointer;
}
.case-toggle:focus-visible {
  outline: 1px solid var(--text);
  outline-offset: -1px;
}

.case-num {
  grid-area: num;
  color: var(--text);
}

.wordmark {
  grid-area: mark;
  position: relative;
  display: block;
  font-family: var(--font-display);
  font-size: var(--display-m);
  line-height: 0.9;
  letter-spacing: 0.01em;
}
/* Card foot: Open case bottom-left, the brand's logo bottom-right as a quiet
   corner mark, single-tone through a mask. Never a focal point. */
.case-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 30px;
}
.case-foot .case-link {
  margin-top: 0;
}
.case-mark {
  display: none;
}
.case-mark.has-art {
  display: block;
  width: 96px;
  height: 44px;
  background: var(--mark);
  -webkit-mask: var(--art) right center / contain no-repeat;
  mask: var(--art) right center / contain no-repeat;
}

.tagline {
  grid-area: tag;
  color: var(--text);
}

.sign {
  grid-area: sign;
  align-self: center;
  width: 18px;
  height: 18px;
  position: relative;
}
.sign::before,
.sign::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: currentColor;
  transition: transform 320ms var(--ease-out);
}
.sign::after {
  transform: rotate(90deg);
}
.case.is-open .sign::after {
  transform: rotate(0deg);
}

/* Panel: grid-rows trick so height animates without measuring. */
.case-summary {
  /* One-sentence teaser, tagline size. Shown collapsed on phones, always on desktop. */
  max-width: 60ch;
  margin: -8px 0 26px;
  font-size: var(--step-0);
  line-height: 1.7;
}
.case-summary.is-placeholder {
  padding: 14px 16px;
  border: 1px dashed var(--rule);
}
/* The teaser already marks the ODYS copy as pending. */
.case .slot-empty {
  display: none;
}
.case-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 420ms var(--ease-out);
}
.case.is-open .case-panel {
  grid-template-rows: 1fr;
}
/* Scoped to the homepage cards' collapsing panel; case pages use .panel-inner too. */
.case-panel > .panel-inner {
  min-height: 0;
  overflow: hidden;
}
.panel-body {
  padding-bottom: 36px;
  clip-path: inset(0 0 100% 0);
  transition: clip-path 480ms steps(12, end) 80ms;
}
.case.is-open .panel-body {
  clip-path: inset(0 0 0 0);
}
.case:not(.is-open) .panel-inner {
  visibility: hidden;
  transition: visibility 0s linear 420ms;
}

.case-meta {
  color: var(--text);
  margin-bottom: 18px;
}
.summary {
  max-width: 60ch;
  font-size: var(--step-0); /* same size and weight as the tagline */
  line-height: 1.7;
}
.summary + .summary {
  margin-top: 14px;
}
.readout {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px 32px;
  margin-top: 26px;
  font-variant-numeric: tabular-nums;
}
.readout .fig {
  margin-right: 6px;
  font-size: var(--step-2);
  letter-spacing: 0;
  color: var(--accent);
}
.readout li::before {
  content: "> ";
  color: var(--mark);
}

.slot-empty {
  max-width: 60ch;
  padding: 22px 20px;
  border: 1px dashed var(--rule);
  color: var(--text);
}
.slot-empty .label {
  display: block;
  color: var(--text);
  margin-bottom: 6px;
}

/* Craft topics: small, quiet previews. Not a gallery. Side-by-side (900px+) only:
   stacked cases expand to the summary alone. */
@media (max-width: 899px) {
  .case .topics {
    display: none;
  }
}
.topics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-top: 30px;
  max-width: 720px;
}
.topic figure {
  display: grid;
  gap: 8px;
}
.thumb {
  position: relative;
  aspect-ratio: 4 / 5;
  border: var(--hair);
  overflow: hidden;
}
.thumb::before,
.thumb::after {
  content: "";
  position: absolute;
  z-index: 1;
  width: 8px;
  height: 8px;
  border: 0 solid var(--text);
}
.thumb::before {
  top: -1px;
  left: -1px;
  border-top-width: 1px;
  border-left-width: 1px;
}
.thumb::after {
  bottom: -1px;
  right: -1px;
  border-bottom-width: 1px;
  border-right-width: 1px;
}
.thumb img,
.thumb video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(0.35) contrast(1.05);
  transition: filter 300ms var(--ease-out);
}
.topic:hover .thumb img,
.topic:hover .thumb video {
  filter: none;
}
/* Transparent cutouts (the phone mockups): whole object, no fill behind it,
   so the card shows through. */
/* Content slideshow: four transparent mockups stacked, pure CSS. One 20s cycle,
   a 5s slot each: 1.2s fade in, hold, then a 1.2s fade out that overlaps the
   next image's fade in, so the crossfade never shows two phones at once.
   Each image drifts while it is on (Ken Burns) and resets while hidden.
   Delays put image 1 already on screen at load; the rest wait their slot. */
.thumb--slides img {
  position: absolute;
  inset: 0;
  opacity: 0;
  animation: slide-fade 20s linear infinite, var(--drift) 20s linear infinite;
  animation-delay: var(--at);
}
.thumb--slides img:nth-child(1) { --drift: drift-up; --at: -1.2s; }
.thumb--slides img:nth-child(2) { --drift: drift-right; --at: 3.8s; }
.thumb--slides img:nth-child(3) { --drift: drift-down; --at: 8.8s; }
.thumb--slides img:nth-child(4) { --drift: drift-left; --at: 13.8s; }
/* 6% of 20s = 1.2s; 25% = the 5s slot. */
@keyframes slide-fade {
  0% { opacity: 0; animation-timing-function: ease-in-out; }
  6% { opacity: 1; }
  25% { opacity: 1; animation-timing-function: ease-in-out; }
  31% { opacity: 0; }
  100% { opacity: 0; }
}
@keyframes drift-up {
  0% { transform: scale(1) translate(0, 1.5%); }
  31%, 100% { transform: scale(1.08) translate(0, -1.5%); }
}
@keyframes drift-right {
  0% { transform: scale(1) translate(-1.5%, 0); }
  31%, 100% { transform: scale(1.08) translate(1.5%, 0); }
}
@keyframes drift-down {
  0% { transform: scale(1) translate(0, -1.5%); }
  31%, 100% { transform: scale(1.08) translate(0, 1.5%); }
}
@keyframes drift-left {
  0% { transform: scale(1) translate(1.5%, 0); }
  31%, 100% { transform: scale(1.08) translate(-1.5%, 0); }
}
/* Reduced motion: the first image, still. No fade, no drift. */
@media (prefers-reduced-motion: reduce) {
  .thumb--slides img {
    animation: none;
  }
  .thumb--slides img:first-child {
    opacity: 1;
  }
}
/* Design boards: the whole board, never cropped, with the same 5% inset on
   every case so the boards read as a matched set. */
.thumb--board img {
  object-fit: contain;
  padding: 5%;
}
/* Inline growth charts: drawn in the site's mono and palette, not the file's. */
.thumb svg {
  display: block;
  width: 100%;
  height: 100%;
  font-family: var(--font-mono);
}
.thumb svg [fill="#0A0A0A"] { fill: var(--bg); }
.thumb svg [fill="#B9D2B1"] { fill: var(--text); }
.thumb svg [stroke="#B9D2B1"] { stroke: var(--text); }
.thumb svg [fill="#D64A02"] { fill: var(--accent); }
.thumb-idx {
  position: absolute;
  left: 8px;
  bottom: 6px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.thumb:not(.has-art) {
  border-style: dashed;
}
.thumb-pending {
  position: absolute;
  inset: auto 8px 26px;
  color: var(--text);
}
.thumb.has-art .thumb-idx,
.thumb.has-art .thumb-pending {
  display: none;
}
.topic figcaption {
  color: var(--text);
}

.case-link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 30px;
  padding: 12px 16px;
  border: 1px solid var(--text);
  text-decoration: none;
  transition: background-color 180ms var(--snap), color 180ms var(--snap);
}
.case-link:hover {
  background: var(--text);
  color: var(--bg);
}
/* Open case on the homepage: orange fills from the center out to both sides;
   a dark copy of the label is revealed by the same clip, so the text turns
   dark exactly as the fill passes. The card's tagline turns orange with it. */
.case-link.has-fill {
  position: relative;
  padding: 0;
  gap: 0;
}
.case-link.has-fill:hover {
  background: none;
  color: inherit;
}
.cl-face,
.cl-fill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
}
.cl-fill {
  display: none;
}
@media (hover: hover) and (pointer: fine) {
  .cl-fill {
    display: inline-flex;
    position: absolute;
    inset: 0;
    background: var(--accent);
    color: var(--bg);
    clip-path: inset(0 50% 0 50%);
    transition: clip-path 420ms var(--ease-out);
  }
  .case-link.has-fill:hover .cl-fill,
  .case-link.has-fill:focus-visible .cl-fill {
    clip-path: inset(0 0 0 0);
  }
  .tagline {
    transition: color 420ms var(--ease-out);
  }
  .case:has(.case-link:hover) .tagline,
  .case:has(.case-link:focus-visible) .tagline {
    color: var(--accent);
  }
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: reduce) {
  .cl-fill,
  .tagline {
    transition: none;
  }
}
.case-link .icon {
  transition: transform 240ms var(--ease-out);
}
.case-link:hover .icon {
  transform: translateX(3px);
}

/* ─── Also worked with ─── */

.also {
  padding-top: clamp(64px, 8vw, 112px);
}
.logos {
  display: grid;
  grid-template-columns: 1fr; /* phones: one name per row */
  border-left: var(--edge);
  border-top: var(--edge);
}
/* Every cell behaves the same. Default: the company name, flat, sage.
   Desktop hover: orange fills from the center out to both sides, covering the
   name, and the logo is revealed in dark on the fill by the same clip.
   Touch screens have no hover: the names simply stay. */
.logo {
  position: relative;
  isolation: isolate;
  display: grid;
  place-items: center;
  min-height: 76px;
  padding: 20px 12px;
  border-right: var(--edge);
  border-bottom: var(--edge);
  color: var(--text);
}
.logo .logo-name {
  font-family: var(--font-mono);
  font-size: var(--step-1);
  letter-spacing: 0.02em;
  white-space: nowrap;
  text-align: center;
}
.logo-art {
  display: none;
}
/* The whole cell is the link: the anchor wraps the name, and its ::before
   stretches over the cell (above the fill and the logo) to catch the click. */
.logo-link {
  text-decoration: none;
}
.logo-link::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
}
.logo:has(.logo-link:focus-visible) {
  outline: 1px solid var(--text);
  outline-offset: -1px;
}
.logo-link:focus-visible {
  outline: none;
}
@media (hover: hover) and (pointer: fine) {
  .logo::after,
  .logo-art {
    clip-path: inset(0 50% 0 50%);
    transition: clip-path 420ms var(--ease-out);
    pointer-events: none;
  }
  .logo::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: var(--accent);
  }
  .logo.has-art .logo-art {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 2;
    margin: auto;
    width: min(168px, 76%);
    height: 48px;
    background: var(--bg);
    -webkit-mask: var(--art) center / contain no-repeat;
    mask: var(--art) center / contain no-repeat;
  }
  .logo:hover::after,
  .logo:hover .logo-art {
    clip-path: inset(0 0 0 0);
  }
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: reduce) {
  .logo::after,
  .logo-art {
    transition: none;
  }
}

/* ─── About ─── */

/* Phones: text, then the photo. Desktop: text left, photo right. */
.about {
  padding-top: var(--section);
}
.about-grid {
  display: grid;
  gap: clamp(32px, 5vw, 64px);
}
.about-copy {
  display: grid;
  align-content: start;
  gap: 20px;
  max-width: 62ch;
}
.about-copy p {
  margin: 0;
}
.about-lede {
  font-size: var(--step-1);
  line-height: 1.55;
}
.about-facts {
  display: grid;
  margin: 12px 0 0;
  border-top: var(--hair);
}
.about-facts > div {
  display: grid;
  gap: 4px;
  padding: 14px 0;
  border-bottom: var(--hair);
}
.about-facts dd {
  margin: 0;
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.about-photo {
  margin: 0;
  width: 100%;
  max-width: 480px; /* stacked on tablets too: never taller than the screen */
}
/* The portrait as a CRT screen: the case pages' bezel (.crt) and screen
   (.crt-screen: scanlines and vignette), set lighter here so the face stays
   sharp, plus a 1px red/cyan split (the #rgb-split filter) and a faint glow.
   Framed by the rig's crosshair corners, coded above like the case media. */
.about-crt {
  box-shadow: 0 0 28px rgb(185 210 177 / 0.06);
}
.about-crt .about-screen {
  width: 100%;
  aspect-ratio: 4 / 5;
}
.about-crt .about-screen::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='3'%3E%3Crect width='4' height='1' fill='%230a0a0a' fill-opacity='0.22'/%3E%3C/svg%3E");
  box-shadow: inset 0 0 44px 4px rgb(10 10 10 / 0.45);
}
.about-img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--panel); /* until (or without) the photo */
  filter: url(#rgb-split);
}
.about-filters {
  position: absolute;
}
.about-crt .about-frame {
  inset: -10px;
  border-color: rgb(185 210 177 / 0.12);
  pointer-events: none;
}
/* The code clears the crosshair frame. */
.about-crt .crt-code {
  bottom: calc(100% + 18px);
  left: -10px;
}
/* Stacked (phones, tablets): room above the photo for its code and frame, so
   they clear the last fact row's rule. */
@media (max-width: 899px) {
  .about-photo {
    margin-top: 28px;
  }
}
/* Face area: four corner brackets and a size readout (js/crt-photo.js). */
.about-track {
  position: absolute;
  z-index: 2;
  left: calc(var(--trk-x, 0.28) * 100%);
  top: calc(var(--trk-y, 0.14) * 100%);
  width: calc(var(--trk-w, 0.44) * 100%);
  height: calc(var(--trk-h, 0.38) * 100%);
  pointer-events: none;
  --arm: 10px;
  --c: rgb(185 210 177 / 0.55);
  background:
    linear-gradient(var(--c), var(--c)) 0 0 / var(--arm) 1px,
    linear-gradient(var(--c), var(--c)) 0 0 / 1px var(--arm),
    linear-gradient(var(--c), var(--c)) 100% 0 / var(--arm) 1px,
    linear-gradient(var(--c), var(--c)) 100% 0 / 1px var(--arm),
    linear-gradient(var(--c), var(--c)) 0 100% / var(--arm) 1px,
    linear-gradient(var(--c), var(--c)) 0 100% / 1px var(--arm),
    linear-gradient(var(--c), var(--c)) 100% 100% / var(--arm) 1px,
    linear-gradient(var(--c), var(--c)) 100% 100% / 1px var(--arm);
  background-repeat: no-repeat;
}
.about-track-code {
  position: absolute;
  bottom: calc(100% + 4px);
  left: 0;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  line-height: 1;
  white-space: nowrap;
  color: rgb(185 210 177 / 0.7);
}
/* Glitch: slices of the photo jump sideways, a scan band rolls through, the
   split widens and the picture hops (all set per step by js/crt-photo.js).
   Hidden at rest. */
.glitch-slice,
.glitch-scan {
  position: absolute;
  inset: 0;
  display: none;
  pointer-events: none;
}
.glitch-slice {
  background: var(--photo) center / cover no-repeat;
  filter: url(#rgb-split-hot);
  clip-path: inset(var(--t, 0%) 0 var(--b, 100%) 0);
  transform: translateX(var(--dx, 0px));
}
.glitch-scan {
  top: var(--scan-y, 50%);
  bottom: auto;
  height: 7%;
  background: linear-gradient(rgb(185 210 177 / 0), rgb(185 210 177 / 0.09), rgb(185 210 177 / 0));
}
.about-crt.is-glitch .glitch-slice,
.about-crt.is-glitch .glitch-scan {
  display: block;
}
.about-crt.is-glitch .about-img {
  transform: translateY(var(--jump, 0px));
}
.about-crt.is-glitch .about-img,
.about-crt.is-warm .about-img {
  filter: url(#rgb-split-hot);
}
/* Warm-up (js/crt-photo.js): every glitch layer drawn once, invisibly, before
   the first burst, so nothing decodes or builds mid-effect. */
.about-crt.is-warm .glitch-slice,
.about-crt.is-warm .glitch-scan {
  display: block;
  opacity: 0.001;
}
.about-crt.is-glitch .about-track {
  transform: translate(var(--trk-dx, 0px), 0);
}
@media (min-width: 720px) {
  .about-facts > div {
    grid-template-columns: 200px 1fr;
    align-items: baseline;
  }
}
@media (min-width: 900px) {
  .about-grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    align-items: start;
  }
  .about-photo {
    max-width: 480px;
    justify-self: end;
    width: 100%;
  }
}

/* About: the role and the two fact labels in orange, nothing else. */
.about .hl {
  color: var(--accent);
}

/* ─── Services ───
   Three tiers in outlined cards (the case cards' gunmetal edge), stacked on
   phones, a row from 900px. No prices. Each card sits on a dark gradient in
   the palette only (near-black, a faint sage and orange tint), deepening by
   tier: Blueprint subtlest, World medium, Universe richest. A deliberate
   exception to the no-gradient rule, here to signal the hierarchy. */
.services {
  padding-top: var(--section);
}
.services .sec-head {
  flex-wrap: wrap;
}
/* The one-line offer, at the About lead's size. */
.services-lede {
  max-width: 62ch;
  margin: 0 0 clamp(24px, 4vw, 40px);
  font-size: var(--step-1);
  line-height: 1.55;
}
.tiers {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.tier {
  position: relative;
  overflow: hidden;
  border: var(--edge);
  background: var(--tier-bg);
}
.tier-01 {
  --tier-bg:
    linear-gradient(165deg, rgb(185 210 177 / 0.035), rgb(185 210 177 / 0) 55%),
    #0d0e0c;
}
.tier-02 {
  --tier-bg:
    linear-gradient(165deg, rgb(185 210 177 / 0.06), rgb(185 210 177 / 0) 50%),
    linear-gradient(345deg, rgb(214 74 2 / 0.06), rgb(214 74 2 / 0) 45%),
    #10110e;
}
.tier-03 {
  --tier-bg:
    linear-gradient(165deg, rgb(185 210 177 / 0.09), rgb(185 210 177 / 0) 50%),
    linear-gradient(345deg, rgb(214 74 2 / 0.13), rgb(214 74 2 / 0) 55%),
    #131510;
}
.tier-body {
  display: grid;
  align-content: start;
  gap: 14px;
  height: 100%;
  box-sizing: border-box;
  padding: 24px 22px 28px;
}
.tier-body p,
.tier-body h3 {
  margin: 0;
}
.tier-name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--step-2);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent);
}
.tier-line {
  font-size: var(--step-0);
  line-height: 1.6;
}
.tier-list {
  display: grid;
  gap: 6px;
  margin: 8px 0 0;
  padding: 14px 0 0;
  list-style: none;
  border-top: var(--hair);
}
.tier-list li {
  padding-left: 3ch; /* a wrapped item lines up under its text, not the // */
  text-indent: -3ch;
}
.tier-list li::before {
  content: "// ";
}
.services-foot {
  display: grid;
  justify-items: start;
  gap: 4px;
  margin-top: 28px;
}
.services-note {
  margin: 0;
  color: var(--text);
}
.services-foot .case-link {
  margin-top: 16px;
}
.tier-filters {
  position: absolute;
}
@media (min-width: 900px) {
  .tiers {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 16px;
  }
  .tier-body {
    padding: 28px 26px 32px;
  }
}

/* Card glitch (js/tier-glitch.js): copies of the card's content, each shown
   as a thin band shifted sideways, the content's red/cyan split widened, a
   scan band, a small hop. The copies sit on the card's own gradient. Hidden at
   rest; drawn once invisibly as a warm-up (.is-warm). */
.tier-slice {
  position: absolute;
  inset: 0;
  display: none;
  background: var(--tier-bg);
  filter: url(#rgb-split-card);
  clip-path: inset(var(--t, 0%) 0 var(--b, 100%) 0);
  transform: translateX(var(--dx, 0px));
  pointer-events: none;
}
.tier-scan {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--scan-y, 50%);
  height: 8%;
  display: none;
  background: linear-gradient(rgb(185 210 177 / 0), rgb(185 210 177 / 0.08), rgb(185 210 177 / 0));
  pointer-events: none;
}
.tier.is-glitch .tier-slice,
.tier.is-warm .tier-slice {
  display: grid; /* the card body's own layout, so every band lines up */
}
.tier.is-glitch .tier-scan,
.tier.is-warm .tier-scan {
  display: block;
}
.tier.is-warm .tier-slice,
.tier.is-warm .tier-scan {
  opacity: 0.001;
}
.tier.is-glitch > .tier-body,
.tier.is-warm > .tier-body {
  filter: url(#rgb-split-card);
}
.tier.is-glitch > .tier-body {
  transform: translateY(var(--jump, 0px));
}

/* ─── Contact ─── */

.contact {
  padding-top: var(--section);
  padding-bottom: clamp(64px, 10vw, 128px);
}
.contact-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--display-l);
  line-height: 0.95;
  margin-bottom: clamp(32px, 6vw, 64px);
}
.channels {
  display: grid;
  border-top: var(--hair);
}
.channel {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px;
  padding: 20px 0;
  border-bottom: var(--hair);
}
.channel a {
  font-size: var(--step--1); /* label size, so value and label read as one line */
  letter-spacing: 0.04em;
  color: var(--accent); /* on the base, orange passes at this size (4.55:1) */
  text-decoration: none;
  overflow-wrap: anywhere;
}
.channel a:hover {
  text-decoration: underline;
}
.standalone {
  margin-top: 28px;
  color: var(--text);
  max-width: 60ch;
}

.site-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 12px 32px;
  padding-block: 28px 36px;
  border-top: var(--hair);
  color: var(--text);
}
.site-foot a {
  text-decoration: none;
}
.site-foot .credit {
  text-transform: none;
  letter-spacing: 0.02em;
}
.site-foot a:hover {
  color: var(--text);
}

/* ───────────────────────── Reveals (light, CSS only) ───────────────────────── */

.js [data-reveal="type"] {
  clip-path: inset(0 100% 0 0);
}
.js [data-reveal="type"].is-in {
  clip-path: inset(0 0 0 0);
  transition: clip-path 640ms steps(22, end);
}
/* Replaying type-ins clip their children, not themselves: an element clipped
   to nothing reads as 0% visible to IntersectionObserver, so the observed
   heading has to stay unclipped. Same 640ms, 22-step wipe. */
.js [data-reveal="type"][data-replay] {
  clip-path: none;
}
.js [data-reveal="type"][data-replay] > * {
  clip-path: inset(0 100% 0 0);
}
.js [data-reveal="type"][data-replay].is-in > * {
  /* Fully typed, the clip opens 24px past the box so a drifting subtitle
     (and its echo) is never cut off. */
  clip-path: inset(-24px -24px -24px -24px);
  transition: clip-path 640ms steps(22, end);
}
.js [data-reveal="rise"] {
  transform: translateY(14px);
}
.js [data-reveal="rise"].is-in {
  transform: none;
  transition: transform 700ms var(--ease-out);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.js [data-reveal="rule"] {
  clip-path: inset(0 100% 0 0);
}
.js [data-reveal="rule"].is-in {
  clip-path: inset(0 0 0 0);
  transition: clip-path 900ms var(--ease-out);
}

/* HUD readout types in once, line by line. */
.js .hud-bl p {
  clip-path: inset(0 100% 0 0);
}
.js.hero-ready .hud-bl p {
  clip-path: inset(0 0 0 0);
  transition: clip-path calc(var(--n) * 28ms) steps(var(--n), end);
  transition-delay: calc(900ms + var(--i) * 380ms);
}
.js .hero-line .st-main > span,
.js .hero-line .st-echo > span {
  clip-path: inset(0 0 100% 0);
  transform: translateY(0.18em);
}
.js.hero-ready .hero-line .st-main > span,
.js.hero-ready .hero-line .st-echo > span {
  clip-path: inset(0 0 -0.2em 0);
  transform: none;
  transition: clip-path 700ms var(--ease-out), transform 800ms var(--ease-out);
  transition-delay: calc(350ms + var(--i) * 140ms);
}
.js .frame {
  opacity: 0;
}
.js.hero-ready .frame {
  opacity: 1;
  transition: opacity 120ms var(--snap) 150ms;
}

/* ───────────────────────── Desktop ───────────────────────── */

@media (min-width: 720px) {
  .topics {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  .logos {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  .logo {
    min-height: 108px;
  }
  .channel {
    grid-template-columns: 200px 1fr;
    align-items: baseline;
  }
  .hud-br {
    top: auto;
    bottom: 84px;
  }
  .topbar .contact-short {
    display: none;
  }
  .topbar .contact-full {
    display: inline;
  }
  .topbar .email-pair {
    display: inline-flex;
  }
}

@media (min-width: 900px) {
  /* The headline sits over the CRT's lit screen: a soft dark halo keeps
     every letter readable against the picture. */
  .hero-line .st-main {
    text-shadow: 0 0 3px rgb(10 10 10 / 0.9), 0 0 22px rgb(10 10 10 / 0.75);
  }
  /* The CRT takes over; the video stays loaded (hidden) as its screen. */
  .crt-canvas,
  .crt-overlay {
    display: block;
  }
  .crt-video {
    display: none;
  }
  .stage.is-flat .crt-video {
    display: block;
  }
  .frame {
    inset: 84px var(--gutter) 76px;
  }
  .hud {
    bottom: 104px;
  }

  /* Two columns side by side. The open one takes the width; the other stays readable. */
  .chooser {
    grid-template-columns: var(--c1, 1fr) var(--c2, 1fr);
    gap: 12px;
    transition: grid-template-columns 520ms var(--ease-out);
  }
  .chooser[data-open="noxys"] {
    --c1: 1.9fr;
  }
  .chooser[data-open="odys"] {
    --c2: 1.9fr;
  }
  .case {
    display: flex;
    flex-direction: column;
    padding: 0 clamp(20px, 2.4vw, 40px) 8px;
    min-width: 0;
  }
  /* The wordmark sizes to the card's width (cqi). The container is the
     heading, which spans the card's content box, not the card: containment
     on the card would stop it sharing rows with its neighbor (see subgrid). */
  .case h3 {
    container-type: inline-size;
  }
  .case.is-clickable {
    cursor: pointer;
  }
  .case-toggle {
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "num mark"
      ". tag";
    column-gap: 18px;
    row-gap: 18px;
    padding-top: 40px;
  }
  .wordmark {
    /* Fits its column: the mark never reaches the rule, even when the column is closed. */
    font-size: min(clamp(3rem, 1.2rem + 4.6vw, 6rem), calc((100cqi - 72px) / 4.8));
  }
  .sign {
    display: none;
  }
  /* Side by side, both cards are always open: name, tagline, one-sentence
     summary, then the four asset tiles. The longer phone content (meta line,
     paragraphs, figures, ODYS placeholder box) stays on phones. */
  .case-summary {
    margin: 22px 0 0;
  }
  /* Equal cards; the Open case button pins to the bottom-left of each. */
  .case.is-open .case-panel {
    flex: 1;
  }
  .case.is-open .panel-inner,
  .case.is-open .panel-body {
    display: flex;
    flex-direction: column;
  }
  .case.is-open .panel-inner {
    height: 100%;
  }
  .case.is-open .panel-body {
    flex: 1;
  }
  .case .case-foot {
    margin-top: auto;
  }
  .case .topics {
    margin-bottom: 32px;
  }
  .case .case-meta,
  .case .summary,
  .case .readout,
  .case .slot-empty {
    display: none;
  }
  .case .topics {
    margin-top: 28px;
  }
  /* Shared rows across both cards (name, summary, tiles and foot): the
     summary row is as tall as the longer summary, so the tiles, their labels
     and the Open case buttons line up side by side. Without subgrid, the
     flex column above still works, just unaligned. */
  @supports (grid-template-rows: subgrid) {
    .chooser {
      grid-template-rows: auto auto 1fr;
      row-gap: 0;
    }
    .case {
      display: grid;
      grid-row: span 3;
      grid-template-rows: subgrid;
    }
    .case-summary {
      align-self: start;
    }
  }
}

/* ───────────────────────── Reduced motion ─────────────────────────
   Nothing slides, types, or spins. State still changes, and it still reads:
   the open case swaps instantly and the hero's CRT holds still (its video still plays). */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .js [data-reveal],
  .js [data-replay] > *,
  .js .hud-bl p,
  .js .hero-line .st-main > span,
  .js .hero-line .st-echo > span,
  .js .frame {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    transition: none !important;
  }
  .case-panel,
  .panel-body,
  .chooser,
  .sign::before,
  .sign::after,
  .case:not(.is-open) .panel-inner {
    transition: none !important;
  }
  .panel-body {
    clip-path: none;
  }
}

/* ───────────────────────── Placeholder case pages ───────────────────────── */

/* ───────────────────────── Case pages ─────────────────────────
   Eight panels: hero, problem, built, strategy, media, result, system, next.
   Base layout (phones, reduced motion, no JS): the panels stack vertically,
   each opened by a hairline divider with crosshair ticks and its index code.
   Desktop with motion (html.h-mode, set by js/case.js): a horizontal track of
   full-viewport panels under a fixed HUD grid. */

.case-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 20px var(--gutter);
}
.case-top a {
  text-decoration: none;
  white-space: nowrap;
}
.case-top a:hover {
  text-decoration: underline;
  text-underline-offset: 0.3em;
}
.case-top .id {
  font-family: var(--font-display);
  font-size: clamp(1rem, 4.6vw, var(--step-1)); /* one line on phones, like the homepage */
  letter-spacing: 0.02em;
}
/* Phones: the top link shortens to "// Back"; the full one sits at the end. */
@media (max-width: 479px) {
  .case-top .wide-only {
    display: none;
  }
}

/* HUD pieces exist only in horizontal mode. */
.hud-grid,
.hud-ruler,
.hud-bar {
  display: none;
}

/* ── Panels, stacked ── */

.panel {
  position: relative;
  max-width: var(--max);
  margin-inline: auto;
  padding: clamp(28px, 5vw, 48px) var(--gutter) clamp(48px, 8vw, 88px);
}
/* Divider: the grid's hairline, with a crosshair tick at each end. */
.panel + .panel {
  border-top: var(--hair);
  margin-top: 0;
}
.panel + .panel::before,
.panel + .panel::after {
  content: "";
  position: absolute;
  top: -6px;
  width: 11px;
  height: 11px;
  background: var(--text);
  -webkit-mask: var(--crosshair) center / 100% 100% no-repeat;
  mask: var(--crosshair) center / 100% 100% no-repeat;
}
.panel + .panel::before {
  left: calc(var(--gutter) - 6px);
}
.panel + .panel::after {
  right: calc(var(--gutter) - 6px);
}
:root {
  --crosshair: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 11'%3E%3Cpath d='M5.5 0v11M0 5.5h11' stroke='black' stroke-width='1'/%3E%3C/svg%3E");
}
.panel-code {
  margin-bottom: clamp(20px, 4vw, 36px);
}

.panel-hero .panel-inner {
  display: grid;
  gap: 18px;
}
.hero-copy {
  display: grid;
  gap: 18px;
  padding-top: clamp(24px, 6vw, 72px);
}
/* The company name: the page's highlight orange, same as the subtitles. */
.case-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--display-l);
  line-height: 0.95;
  letter-spacing: 0.01em;
  color: var(--accent);
}
.case-tagline {
  font-size: var(--step-1);
}
/* 3D logo slot: an empty, centered, square stage. A dashed hairline marks
   the reserved space until the renderer mounts here. */
.logo-3d {
  justify-self: center;
  width: min(100%, 520px);
  aspect-ratio: 1;
  margin-top: clamp(32px, 6vw, 72px);
  border: 1px dashed var(--rule);
}

.story .panel-inner {
  display: grid;
  gap: 14px;
}
/* Section subtitle: NYXERIN, well above body size, in orange: the one
   orange moment of each section. A small mono index tag sits with it. */
.story-label {
  align-self: start;
  display: grid;
  gap: 12px;
  justify-items: start;
}
.story-tag {
  color: var(--text);
}
.story-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.75rem, 1rem + 1.6vw, 2.5rem);
  line-height: 1.02;
  letter-spacing: 0.01em;
  color: var(--accent);
}
/* Mouse-reactive titles (js/drift.js sets --mx, --my, --blur on .drift):
   used on exactly the hero headline, "Build yours.", the case names, and the
   case subtitles. The main layer drifts toward the cursor on a spring curve
   that overshoots a touch and settles; the echo, a faded copy behind it,
   follows 0.1s later and carries the motion blur. Mouse only: touch and
   reduced motion get the plain title. */
.drift {
  position: relative;
  display: block;
  isolation: isolate;
}
.st-main {
  display: block;
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
  transition: transform 520ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.st-echo {
  display: none;
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .st-echo {
    display: block;
    position: absolute;
    inset: 0;
    z-index: -1;
    opacity: 0.3;
    pointer-events: none;
    transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
    filter: blur(var(--blur, 0px));
    transition:
      transform 620ms cubic-bezier(0.34, 1.56, 0.64, 1) 100ms,
      filter 220ms ease-out 100ms;
  }
  .st-main {
    filter: blur(calc(var(--blur, 0px) * 0.15));
    transition:
      transform 520ms cubic-bezier(0.34, 1.56, 0.64, 1),
      filter 300ms ease-out;
  }
}
@media (prefers-reduced-motion: reduce) {
  .st-main {
    transform: none;
    transition: none;
  }
}
.story-body {
  max-width: 64ch;
}
.story-body p + p {
  margin-top: 1.1em;
}

/* Result figures. */
.figures {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px 32px;
  margin-bottom: 32px;
  font-variant-numeric: tabular-nums;
}
.figures li {
  display: grid;
  gap: 4px;
  justify-items: start;
}
.figures .fig {
  display: inline-block;
  font-size: var(--step-2);
  line-height: 1.1;
  color: var(--accent); /* the key figures, in orange; their labels stay sage */
}

/* Skills named in the copy: filled sage chips with dark text, the page's
   highlight treatment for terms. */
.skill {
  padding: 1px 8px 2px;
  background: var(--text);
  color: var(--bg);
  white-space: nowrap;
}


.case-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.case-cta .case-link {
  margin-top: 0;
  font-size: var(--step-0);
}
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
}
.back-link:hover {
  text-decoration: underline;
  text-underline-offset: 0.3em;
}
.panel-cta .site-foot {
  margin-top: clamp(64px, 10vw, 120px);
  padding-inline: 0;
}
/* Visit the client: bottom-left. Stacked layout: its own row, taking the
   footer's gap, just above it. Horizontal mode: the first item of the HUD
   bar, on every panel. */
.client-link-foot {
  display: flex;
  width: fit-content;
  margin-top: clamp(64px, 10vw, 120px);
}
html:not(.h-mode) .client-link-foot + .site-foot {
  margin-top: 28px;
}
.client-link-hud,
.h-mode .client-link-foot {
  display: none;
}
.h-mode .client-link-hud {
  display: inline-flex;
  flex: none;
  margin-top: 0;
}
.h-mode .client-link-hud .cl-face,
.h-mode .client-link-hud .cl-fill {
  padding: 9px 14px;
}
.client-link:hover .icon {
  transform: translate(2px, -2px);
}

@media (min-width: 900px) {
  .story .panel-inner {
    grid-template-columns: minmax(260px, 5fr) 7fr;
    column-gap: clamp(32px, 5vw, 80px);
  }
  .figures {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* ── Media showcase: three CRT viewers ──
   DES, VID, CNT left to right, each showing one item at a time with its own
   prev / next arrows. Each screen keeps its zone's shape (A4, 9:16, 4:5) and
   is sized to its column's width and height (container units), so all three
   fit any desktop viewport. A light vertical offset steps them down left to
   right. CRT treatment: bezel, scanlines (an SVG pattern, not a gradient),
   inset vignette, the item code top-left, the category underneath. */

.panel-media .panel-inner {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: clamp(20px, 3vw, 36px);
}
.collage {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: clamp(16px, 2.4vw, 40px);
  height: clamp(520px, 62vw, 760px); /* the vertical (non-track) desktop layout */
  min-height: 0;
}
.viewer {
  container-type: size;
  display: grid;
  grid-template-columns: auto auto auto;
  grid-template-rows: auto auto;
  justify-content: center;
  align-content: start;
  column-gap: 10px;
  row-gap: 10px;
  min-height: 0;
}
.viewer-btn {
  all: unset;
  align-self: center;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--text);
  color: var(--text);
  cursor: pointer;
  transition: background-color 180ms var(--snap), color 180ms var(--snap);
}
.viewer-btn:hover {
  background: var(--text);
  color: var(--bg);
}
.viewer-btn:focus-visible {
  outline: 1px solid var(--text);
  outline-offset: 3px;
}
.viewer-name {
  grid-column: 2;
  justify-self: start;
  margin: 0;
  letter-spacing: 0.16em;
}
.crt {
  position: relative;
  margin: 0;
  padding: 5px;
  border: 1px solid var(--rule);
  background: var(--bg);
}
.crt-screen {
  position: relative;
  overflow: hidden;
  background: var(--bg);
}
/* Screen sizes: as wide as the column allows after the arrows (92px), and
   no taller than the column allows after the offset and the name. */
/* One frame for all three: 3:4, the same size and baseline in every column;
   each media covers it (cropping to fit). */
.crt-screen { width: min(100cqw - 92px, 62cqh); aspect-ratio: 3 / 4; }
.slide,
.slide img,
.slide video {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}
.slide[hidden] {
  display: none;
}
.slide img,
.slide video {
  object-fit: cover;
}
/* Per-image crop: anchor to the bottom, so a tall screenshot's header sits
   above the frame and its content fills it. */
.slide img.crop-bottom {
  object-position: 50% 100%;
}
.svg-slot,
.svg-slot svg {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}
.svg-slot svg { font-family: var(--font-mono); }
.svg-slot [fill="#0A0A0A"] { fill: var(--bg); }
.svg-slot [fill="#B9D2B1"] { fill: var(--text); }
.svg-slot [stroke="#B9D2B1"] { stroke: var(--text); }
.svg-slot [fill="#D64A02"] { fill: var(--accent); }
/* CNT only: the shown image drifts like the homepage content tile (slow
   zoom 1 to 1.08 plus a pan, direction varying per slide). */
@media (prefers-reduced-motion: no-preference) {
  .viewer-cnt .slide > * {
    animation: var(--kb) 10s ease-in-out infinite alternate;
  }
  .viewer-cnt .slide:nth-child(4n + 1) > * { --kb: kb-up; }
  .viewer-cnt .slide:nth-child(4n + 2) > * { --kb: kb-right; }
  .viewer-cnt .slide:nth-child(4n + 3) > * { --kb: kb-down; }
  .viewer-cnt .slide:nth-child(4n + 4) > * { --kb: kb-left; }
}
@keyframes kb-up { from { transform: scale(1) translate(0, 1.5%); } to { transform: scale(1.08) translate(0, -1.5%); } }
@keyframes kb-right { from { transform: scale(1) translate(-1.5%, 0); } to { transform: scale(1.08) translate(1.5%, 0); } }
@keyframes kb-down { from { transform: scale(1) translate(0, -1.5%); } to { transform: scale(1.08) translate(0, 1.5%); } }
@keyframes kb-left { from { transform: scale(1) translate(1.5%, 0); } to { transform: scale(1.08) translate(-1.5%, 0); } }
/* Scanlines and vignette over the picture. */
.crt-screen::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='3'%3E%3Crect width='4' height='1' fill='%230a0a0a' fill-opacity='0.38'/%3E%3C/svg%3E");
  box-shadow: inset 0 0 36px 6px rgb(10 10 10 / 0.6);
}
/* The code sits just outside the frame's top edge, never on the artwork, so
   burned-in captions and chart labels stay uncovered. */
.crt-code {
  position: absolute;
  z-index: 2;
  bottom: calc(100% + 6px);
  left: -1px;
  padding: 0;
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  line-height: 1.4;
  white-space: nowrap;
  color: var(--text);
}
.crt-code span {
  color: rgb(185 210 177 / 0.6);
}
.crt-code.is-active {
  color: var(--accent);
}
/* Play button: phones and reduced motion only (no autoplay there). */
.viewer-play {
  all: unset;
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 50%;
  display: none;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border: 1px solid var(--text);
  background: rgb(10 10 10 / 0.82);
  color: var(--text);
  transform: translate(-50%, -50%);
  cursor: pointer;
}
.viewer-play:focus-visible {
  outline: 1px solid var(--text);
  outline-offset: 3px;
}
.viewer.is-manual:not(.is-playing) .viewer-play {
  display: inline-flex;
}

/* ── Horizontal mode (desktop, motion allowed) ── */

html.h-mode,
html.h-mode body {
  height: 100%;
  overflow: hidden;
}
.h-mode .case-top {
  position: fixed;
  inset: 0 0 auto;
  z-index: 5;
}

.h-mode .track {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior: contain; /* js/case.js drives and settles the track; no CSS snap */
  scrollbar-width: none;
}
.h-mode .track::-webkit-scrollbar {
  display: none;
}
.h-mode .track:focus {
  outline: none;
}
.h-mode .panel {
  flex: 0 0 100vw;
  max-width: none;
  height: 100%;
  margin: 0;
  display: grid;
  align-items: center;
  padding: 112px var(--gutter) 120px;
  overflow-y: auto;
  scrollbar-width: none;
  border: 0;
}
.h-mode .panel::before,
.h-mode .panel::after,
.h-mode .panel-code {
  display: none;
}
.h-mode .panel-inner {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
}
.h-mode .panel-hero .panel-inner {
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: clamp(32px, 5vw, 80px);
}
.h-mode .hero-copy {
  padding-top: 0;
}
.h-mode .logo-3d {
  width: min(520px, 54vh, 40vw);
  margin-top: 0;
}
/* 3D logo, live: layered behind the case-file text like the homepage hero,
   filling the right of the panel and reaching back under the title. The
   canvas is transparent; the dashed placeholder border goes. */
.h-mode .panel-hero {
  isolation: isolate;
}
.h-mode .panel-hero .hero-copy {
  position: relative;
  z-index: 1;
}
/* Placed and sized by js/logo3d.js, right of the title, which stays above it.
   The canvas's transparent margin may run past the edge, so the hero clips
   horizontally. */
.h-mode .panel-hero {
  overflow-x: hidden;
}
.h-mode .panel-hero .logo-3d.is-live {
  position: absolute;
  z-index: 0;
  left: var(--logo-x, 30%);
  top: var(--logo-y, 4%);
  width: var(--logo-w, auto);
  height: var(--logo-h, 92%);
  aspect-ratio: 1;
  margin: 0;
  border: 0;
  pointer-events: none;
}
.h-mode .panel-media .panel-inner {
  height: 100%;
  align-self: stretch;
}
.h-mode .collage {
  height: auto;
}
.h-mode .panel-cta .panel-inner {
  display: grid;
  align-content: center;
  min-height: 100%;
}

/* Grid: hairlines at low opacity with an L-shaped mark at each vertex, behind
   the content, drawn on one canvas (js/case.js). The spacing opens up around
   the mouse and relaxes back; the marks travel with their vertices but never
   change size. No CSS gradients. */
.h-mode .hud-grid {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.logo-3d.is-live {
  position: relative; /* h-mode places it absolutely; either way it anchors the overlay */
  border-color: transparent;
}
.logo-3d-canvas {
  display: block;
  width: 100%;
  height: 100%;
}
/* The glitch burst's box outlines and labels, drawn over the WebGL canvas. */
.logo-3d-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.hud-lines {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* Top ruler: coordinates every grid column, reading the track position. */
.h-mode .hud-ruler {
  display: block;
  position: fixed;
  top: 66px;
  left: 0;
  right: 0;
  z-index: 4;
  height: 16px;
  overflow: hidden;
  pointer-events: none;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
}
/* The whole coordinate strip reads in orange, at full strength. */
.hud-ruler span {
  position: absolute;
  top: 0;
  padding-left: 5px;
  color: var(--accent);
}

/* Bottom readout: index (active number in orange), section code, progress,
   track coordinate, and the arrows. */
.h-mode .hud-bar {
  display: flex;
  position: fixed;
  inset: auto 0 0;
  z-index: 5;
  align-items: center;
  gap: clamp(16px, 2.4vw, 32px);
  padding: 22px var(--gutter);
  font-variant-numeric: tabular-nums;
}
.hud-index b {
  font-weight: 400;
  color: var(--accent);
}
.hud-code {
  min-width: 22ch;
}
.hud-progress {
  position: relative;
  flex: 1;
  height: 1px;
  background: var(--rule);
}
.hud-progress i {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--p, 0%);
  background: var(--text);
}
.hud-ticks span {
  position: absolute;
  top: -4px;
  width: 1px;
  height: 9px;
  background: var(--text);
}
.hud-ticks span.is-active {
  top: -6px;
  width: 3px;
  height: 13px;
  margin-left: -1px;
  background: var(--accent);
}
.hud-arrows {
  display: flex;
  gap: 8px;
}
.hud-btn {
  all: unset;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--text);
  cursor: pointer;
  transition: background-color 180ms var(--snap), color 180ms var(--snap);
}
.hud-btn:hover {
  background: var(--text);
  color: var(--bg);
}
.hud-btn:focus-visible {
  outline: 1px solid var(--text);
  outline-offset: 3px;
}
.hud-btn[disabled] {
  border-color: var(--rule);
  color: var(--rule);
  cursor: default;
  background: none;
}

/* ── Case pages, mobile (< 900px): asymmetric, editorial ──
   A hairline rail runs down the left edge with a dot at each paragraph and
   a crosshair at each divider; index codes run vertically down the right
   edge. Each section opens with a big index number and a display title at
   its own size and offset, so no two sections sit the same way. Ticket
   framing (bordered panel, dashed perforation, barcode) only on the hero
   and the closing panel. Nothing centered. */

.story-num,
.ticket-head {
  display: none;
}

/* The email address, twice over: the address itself is a plain mailto link;
   the small copy button right after it copies the address (js/copy-email.js)
   and this tag confirms it, in the meta-label voice, fixed at the button for
   a moment. The button is a hairline box, so it never reads as part of the
   link. */
.email-pair {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  gap: 10px;
}
.copy-btn {
  all: unset;
  box-sizing: border-box;
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 22px;
  height: 22px;
  border: var(--hair);
  color: var(--text);
  cursor: pointer;
  font-size: 12px; /* the icon is 1em */
  transition: background-color 180ms var(--snap), color 180ms var(--snap);
}
.copy-btn:hover {
  background: var(--text);
  color: var(--bg);
}
.copy-btn:focus-visible {
  outline: 1px solid var(--text);
  outline-offset: 2px;
}
.copy-btn .icon {
  fill: none;
  stroke-width: 1.3;
}
.copied-tag {
  position: fixed;
  z-index: 50;
  padding: 3px 8px;
  border: var(--hair);
  background: var(--bg);
  color: var(--text);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translateY(3px);
  transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out);
}
.copied-tag.is-on {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .copied-tag {
    transform: none;
    transition: none;
  }
}

/* ───────── Line rig: shared drawing pieces ─────────
   The technical-drawing vocabulary of the case pages' phone rig (below) and
   the homepage's background field (.field): crosshair registration marks on
   a frame, hollow 7px boxes, small mono codes, a diagonal with a box, and the
   moving assembly (an arm carrying a node square and its fixed-length branch
   line, ridden by ride-v / ride-h). Each user sets the layout; these set the
   look. */
.rig-frame {
  position: absolute;
  inset: 16px 24px 22px 20px;
  border: 1px solid rgb(185 210 177 / 0.16);
}
.rig-frame b,
.field-mark {
  position: absolute;
  width: 11px;
  height: 11px;
  background: var(--text);
  -webkit-mask: var(--crosshair) center / 100% 100% no-repeat;
  mask: var(--crosshair) center / 100% 100% no-repeat;
}
.rig-frame b:nth-child(1) { top: -6px; left: -6px; }
.rig-frame b:nth-child(2) { top: -6px; right: -6px; }
.rig-frame b:nth-child(3) { bottom: -6px; left: -6px; }
.rig-frame b:nth-child(4) { bottom: -6px; right: -6px; }
.box {
  position: absolute;
  width: 7px;
  height: 7px;
  border: 1px solid var(--text);
  background: var(--bg);
}
.box-a { top: -4px; left: 64%; }
.box-b { right: -4px; top: 38%; }
.rig-code {
  position: absolute;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  line-height: 1;
  color: rgb(185 210 177 / 0.6);
}
.rig-code-a { bottom: 28px; left: 30px; }
.rig-code-b { top: 22px; left: calc(64% + 14px); }
/* Static, so it stays in the empty bottom-right corner, clear of the copy. */
.rig-diag {
  position: absolute;
  right: 24px;
  bottom: 22px;
  width: 58px;
  height: 1px;
  background: rgb(185 210 177 / 0.35);
  transform: rotate(34deg);
  transform-origin: right center;
}
.rig-diag i {
  position: absolute;
  left: -3px;
  top: -3px;
  width: 7px;
  height: 7px;
  border: 1px solid var(--text);
  background: var(--bg);
}

/* The connected assembly. */
.arm {
  position: absolute;
  will-change: transform;
  animation: var(--ride) var(--dur) ease-in-out infinite alternate;
}
.node {
  position: absolute;
  width: 7px;
  height: 7px;
  background: var(--text);
}
/* Fixed length: a branch never scales, it only rides along with its arm. */
.branch {
  position: absolute;
  background: var(--line);
}
.node-code {
  position: absolute;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  line-height: 1;
  color: var(--text);
}

/* ───────── Homepage fields ─────────
   Each homepage section carries its own field: the rig's pieces sprinkled
   behind its content (positions set inline: x in % of the window's width, y
   in % of the section's height), locked to the section so they scroll with
   it. Faint enough never to compete with the copy or the CRT: the layer's
   opacity scales all of it down. Only orthogonal lines: no diagonals.
   Motion: the arms ride their rails like the case pages' (ride-v / ride-h),
   and the dimension lines slowly extend (field-extend), transform only.
   Reveal (js/reveal.js, data-reveal="field"): when the section scrolls in,
   rails and dimension lines draw open like a rule and the rest fades in, one
   piece after another. Orange is an opt-in accent (.is-accent): one node in
   the hero, one box at the contact. Phones keep a mark or two per section.
   The case pages carry the same fields, one per panel (desktop only; phones
   keep the rig), with data-replay: drawn in each time the panel becomes
   active, reset once it is fully out of view. */
.hero > .field {
  inset: 0;
}
/* Sections are narrower than the window (.wrap): their fields reach out to
   the window's edges, and the page is clipped sideways so they never add a
   horizontal scroll. */
body:has(.field) {
  overflow-x: clip;
}
section:has(> .field) {
  position: relative;
  isolation: isolate; /* keeps the field's z-index: -1 inside the section */
}
section > .field {
  top: 0;
  bottom: 0;
  left: calc(50% - 50vw);
  right: calc(50% - 50vw);
}
.field {
  position: absolute;
  z-index: -1; /* under the section's content (in the hero: the CRT too) */
  overflow: hidden;
  pointer-events: none;
  opacity: 0.5;
  --line: rgb(185 210 177 / 0.3);
  --dur: 18s;
}
.field > * {
  position: absolute;
}
.field-mark {
  margin: -6px 0 0 -6px; /* centered on its point */
}
/* Scroll-in: lines draw open, the rest fades in, staggered. */
.js .field[data-reveal="field"] > * {
  opacity: 0;
}
.js .field[data-reveal="field"] > :is(.field-rail-v) {
  opacity: 1;
  clip-path: inset(0 0 100% 0);
}
.js .field[data-reveal="field"] > :is(.field-rail-h, .field-dim) {
  opacity: 1;
  clip-path: inset(-20px 100% -20px -20px);
}
.js .field[data-reveal="field"].is-in > * {
  opacity: 1;
  clip-path: inset(-20px -20px -20px -20px);
  transition: opacity 600ms var(--ease-out), clip-path 900ms var(--ease-out);
  transition-delay: calc(var(--n, 0) * 90ms + 150ms);
}
.field > :nth-child(2) { --n: 1; }
.field > :nth-child(3) { --n: 2; }
.field > :nth-child(4) { --n: 3; }
.field > :nth-child(5) { --n: 4; }
.field > :nth-child(6) { --n: 5; }
.field > :nth-child(7) { --n: 6; }
.field > :nth-child(8) { --n: 7; }
.field > :nth-child(9) { --n: 8; }
.field > :nth-child(10) { --n: 9; }
.field > :nth-child(n + 11) { --n: 10; }
.field-rail {
  background: rgb(185 210 177 / 0.24);
}
.field-rail-v {
  width: 1px;
}
.field-rail-h {
  height: 1px;
}
/* A rail node and its fixed-length branch: right off the vertical rail,
   down off the horizontal one. */
.field-arm-v {
  --ride: ride-v;
  width: 0;
}
.field-arm-v .node { left: -3px; top: 0; }
.field-arm-v .branch {
  left: 7px;
  top: 3px;
  width: var(--reach, 9vw);
  height: 1px;
}
.field-arm-h {
  --ride: ride-h;
  height: 0;
  animation-delay: -7s;
}
.field-arm-h .node {
  left: -3px;
  top: -3px;
}
.field .node.is-accent {
  background: var(--accent);
}
.field .box.is-accent {
  border-color: var(--accent);
}
.field > .box {
  margin: -4px 0 0 -4px; /* centered on its point */
}
.field-arm-h .branch {
  left: 0;
  top: 7px;
  width: 1px;
  height: var(--hang, 9vh);
}
.field-arm-h .node-code { left: 8px; top: -14px; }
/* Dimension line: end ticks, a line that extends to --len and back, and its
   far tick riding the line's end. */
.field-dim {
  width: var(--len);
  height: 7px;
  border-left: 1px solid var(--line);
}
.field-dim .dim-line {
  position: absolute;
  left: 0;
  top: 3px;
  width: 100%;
  height: 1px;
  background: var(--line);
  transform-origin: left center;
  animation: field-extend 22s ease-in-out infinite alternate;
}
.field-dim .dim-end {
  position: absolute;
  right: 0;
  top: 0;
  width: 1px;
  height: 7px;
  background: var(--line);
  animation: field-extend-end 22s ease-in-out infinite alternate;
}
.field-dim .rig-code {
  left: 6px;
  top: 10px;
}
/* The vertical one: the same piece, turned to run down the page. */
.field-dim-v {
  transform: rotate(90deg);
  transform-origin: left top;
}
.field-dim-v .dim-line,
.field-dim-v .dim-end {
  animation-delay: -9s;
}
@keyframes field-extend {
  from { transform: scaleX(0.55); }
  to { transform: scaleX(1); }
}
@keyframes field-extend-end {
  from { transform: translateX(calc(var(--len) * -0.45)); }
  to { transform: translateX(0); }
}
@media (max-width: 899px) {
  .field-wide {
    display: none;
  }
  /* Case pages: phones have the rig instead. */
  .case-page .field {
    display: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .field-dim .dim-line,
  .field-dim .dim-end {
    animation: none;
  }
  .js .field[data-reveal="field"] > *,
  .js .field[data-reveal="field"].is-in > * {
    opacity: 1;
    clip-path: none;
    transition: none;
  }
}

/* Vertical coordinate ruler down the left edge (900px+): the case pages' top
   ruler (.hud-ruler), turned. A mark every 120px, each reading the page's
   scroll position plus its own offset (js/site.js). */
.vruler {
  display: none;
}
@media (min-width: 900px) {
  .vruler {
    display: block;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 4;
    width: 16px;
    overflow: hidden;
    pointer-events: none;
    font-size: 0.75rem;
    letter-spacing: 0.06em;
    font-variant-numeric: tabular-nums;
  }
  .vruler span {
    position: absolute;
    left: 0;
    padding-top: 5px;
    writing-mode: vertical-rl;
    color: var(--accent);
  }
}

@media (max-width: 899px) {
  .case-page .track {
    position: relative;
  }
  /* The rail. */
  .case-page .track::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 8px;
    width: 1px;
    background: var(--rule);
  }
  .case-page .panel {
    padding: 36px 30px 78px 28px; /* the bottom band holds the corner line, clear of the copy */
  }
  /* Divider crosshairs sit on the rail and on the right edge. */
  .case-page .panel + .panel::before {
    left: 3px;
  }
  .case-page .panel + .panel::after {
    right: 3px;
  }
  /* Index codes run down the right margin. */
  .case-page .panel-code {
    position: absolute;
    top: 36px;
    right: 6px;
    margin: 0;
    writing-mode: vertical-rl;
    font-size: 0.6875rem;
    letter-spacing: 0.16em;
  }

  /* Ticket head: code left, barcode right, a dashed perforation under it. */
  .ticket-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding-bottom: 12px;
    margin-bottom: 22px;
    border-bottom: 1px dashed var(--rule);
    font-size: 0.6875rem;
  }
  .barcode {
    flex: none;
    width: 92px;
    height: 22px;
    fill: var(--text);
  }

  /* Hero: a bordered ticket, title hard left, the rest stepped off it. */
  .panel-hero .panel-inner {
    grid-template-columns: minmax(0, 1fr); /* nothing may push the ticket wider */
    gap: 0;
    padding: 14px 14px 18px;
    border: 1px solid var(--rule);
  }
  .panel-hero .hero-copy {
    min-width: 0;
    padding-top: 0;
    gap: 14px;
  }
  /* The ticket head already says "Case file". */
  .panel-hero .hero-copy > .label:first-child {
    display: none;
  }
  .panel-hero .case-title {
    font-size: clamp(2.75rem, 13.5vw, 5rem);
    line-height: 0.9;
  }
  .panel-hero .case-tagline {
    margin-left: 18%;
    font-size: var(--step-0);
  }
  .panel-hero .case-disciplines {
    justify-self: end;
    max-width: 24ch;
    text-align: right;
  }
  /* The 3D logo is desktop only (js/logo3d.js skips it here too). */
  .panel-hero .logo-3d {
    display: none;
  }

  /* Section headers: big number, then the title, each section its own scale. */
  .case-page .story-label {
    gap: 6px;
  }
  /* The index number: one size, anchored top-left, on every section. */
  .case-page .story-num {
    display: block;
    justify-self: start;
    font-family: var(--font-display);
    font-size: 4.5rem;
    line-height: 0.85;
    color: var(--text);
  }
  .case-page .story-tag {
    font-size: 0.6875rem;
  }
  .case-page .story .panel-inner {
    gap: 22px;
  }
  .case-page .story-body {
    position: relative;
  }
  .case-page .story-body p {
    position: relative;
  }
  /* A dot on the rail at each paragraph. 28px panel padding + the body's
     own offset, back to the rail at 8px. */
  .case-page .story-body p::before {
    content: "";
    position: absolute;
    top: 0.62em;
    left: calc(5.5px - 28px - var(--off, 0px));
    width: 5px;
    height: 5px;
    background: var(--text);
  }

  /* 02 The problem: large title, body stepped in. */
  #s-problem .story-title { font-size: 2.6rem; }
  .story:has(#s-problem) .story-body { --off: 36px; margin-left: var(--off); }

  /* 03 What I built: medium title, body full width. */
  #s-built .story-title { font-size: 2.1rem; }
  .story:has(#s-built) .story-body { --off: 0px; }

  /* 04 The strategy: title indented, body indented further. */
  #s-strategy .story-title { font-size: 2.35rem; margin-left: 12%; }
  .story:has(#s-strategy) .story-body { --off: 52px; margin-left: var(--off); }

  /* 06 The result: figures in a bordered ticket grid. */
  #s-result .story-title { font-size: 2.25rem; }
  .story:has(#s-result) .story-body { --off: 0px; }
  .case-page .figures {
    gap: 0;
    border-top: 1px solid var(--rule);
    border-left: 1px solid var(--rule);
    margin-bottom: 26px;
  }
  .case-page .figures li {
    padding: 12px 12px 14px;
    border-right: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
  }
  .case-page .figures li:nth-child(odd) {
    padding-left: 12px;
  }

  /* 07 The system: smallest title (the longest), body stepped in a little. */
  #s-skills .story-title { font-size: 1.6rem; max-width: 12ch; }
  .story:has(#s-skills) .story-body { --off: 20px; margin-left: var(--off); }


  /* Media: the three viewers stack, each offset a little differently, with
     the arrows either side of the screen. */
  #s-media .story-title { font-size: 2.2rem; }
  .collage {
    display: grid;
    grid-template-columns: 1fr;
    row-gap: 34px;
    height: auto;
  }
  .viewer {
    container-type: inline-size;
    justify-content: start;
    padding-top: 0;
  }
  .crt-screen { width: min(100cqw - 92px, 250px); }

  /* Closing ticket. */
  .panel-cta .panel-inner {
    padding: 14px 14px 18px;
    border: 1px solid var(--rule);
  }
  .panel-cta .case-cta {
    flex-direction: column;
    align-items: flex-start;
    gap: 20px;
  }
  .panel-cta .back-link {
    align-self: flex-end;
  }
  .panel-cta .site-foot {
    margin-top: 32px;
    padding-top: 16px;
    border-top: 1px dashed var(--rule);
  }

  /* ── The rig: HUD furniture behind each section ──
     A light frame with crosshair corners, hollow boxes on its lines, index
     codes, the odd diagonal, and one connected assembly per section: a node
     square riding a parent line (the left rail for .rig-v, the frame's top
     edge for .rig-h) with a branch line growing from it across the text.
     The arm carries node and branch together, so the parent line drives the
     node and the node drives the branch, which travels with it
     at a fixed length: only its position moves. Transform only. Clipped to
     the section.
     The main text is a no-line zone: js/case.js measures it and the arms
     move only in the band above or below it (--zone-start / --zone-size,
     --drop). Frame, rail, boxes, and codes already sit in the margins. */
  .case-page .panel-inner {
    position: relative;
    z-index: 1;
  }
  .rig {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
    --line: rgb(185 210 177 / 0.22); /* lines that cross copy: thin and faint */
    --dur: 16s;
  }

  /* Vertical parent (the rail at x = 8px): the node slides along the rail and
     its branch runs, at a constant width, right to the outline box's border. */
  .arm-v {
    --ride: ride-v;
    top: var(--zone-start, 0px);
    left: 0;
    width: 100%;
    height: var(--zone-size, 0px); /* the node's whole travel stays in the band */
  }
  .arm-v .node { left: 5px; top: 0; }
  .arm-v .branch {
    left: 12px;
    top: 3px;
    right: 24px; /* the frame's right border */
    height: 1px;
  }
  /* Hero and closing panels have a ticket instead of a frame: end at its border. */
  .rig:not(:has(.rig-frame)) .arm-v .branch {
    right: 30px;
  }
  /* No label on rail nodes: in the top band it would sit on the title. */
  .arm-v .node-code { display: none; }

  /* Horizontal parent (the frame's top edge): the node slides along it and
     its branch hangs at a constant length, down to the subtitle's top edge. */
  .arm-h {
    --ride: ride-h;
    top: 13px;
    left: 20px;
    right: 24px;
    height: calc(100% - 13px);
  }
  .arm-h .node { left: -3px; top: 0; }
  .arm-h .branch {
    left: 0;
    top: 8px;
    height: var(--drop, 0px); /* measured: ends at the subtitle's top edge */
    width: 1px;
  }
  .arm-h .node-code { left: 8px; top: -2px; }

  /* Phase offsets so neighbouring sections never move in lockstep. */
  .panel:nth-child(2n) .arm {
    animation-delay: -6s;
  }
  .panel:nth-child(3n) .arm {
    animation-delay: -11s;
  }
}

@keyframes ride-v {
  from { transform: translateY(0); }
  to { transform: translateY(100%); }
}
@keyframes ride-h {
  from { transform: translateX(12%); }
  to { transform: translateX(82%); }
}

/* Desktop never shows the rig; reduced motion holds it still. */
@media (min-width: 900px) {
  .rig {
    display: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .arm {
    animation: none !important;
  }
  .arm-v { transform: translateY(50%); }
  .arm-h { transform: translateX(46%); }
}

.stub {
  min-height: 100svh;
  display: grid;
  align-content: center;
  gap: 28px;
  padding-block: 96px;
}
.stub-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--display-l);
  line-height: 0.95;
}
.stub p {
  max-width: 52ch;
  color: var(--text);
}
.stub .case-link {
  justify-self: start;
  margin-top: 0;
}

