/* ═══════════════════════════════════════════════════════════════════════════
   Mergestro — hand-drawn technical zine
   Self-contained: no CDN, no remote fonts, no analytics. Everything ships here.
   Frames are generated as wobbled SVG paths at runtime (app.js); this file owns
   the paper, the ink, the highlighters, and the rhythm.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Faces ───────────────────────────────────────────────────────────────── */
@font-face {
  font-family: 'Shantell Sans';
  src: url('fonts/shantell-sans-latin-wght-normal.woff2') format('woff2-variations');
  font-weight: 300 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Figtree';
  src: url('fonts/figtree-latin-wght-normal.woff2') format('woff2-variations');
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('fonts/jetbrains-mono-latin-400-normal.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('fonts/jetbrains-mono-latin-700-normal.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ── Tokens ──────────────────────────────────────────────────────────────── */
:root {
  --paper:      #FFF8EC;
  --paper-2:    #F7EEDC;   /* recessed paper — the ink bands              */
  --ink:        #111111;
  --ink-soft:   #3E3A33;   /* body text: tinted from the ground, not gray */
  --ink-mute:   #6A6157;   /* captions — 4.9:1 on paper                   */
  --yellow:     #FFE34D;
  --pink:       #FF6BAE;
  --teal:       #00C7B7;
  --teal-deep:  #00776D;   /* teal is too light for text — this reads     */
  --pink-deep:  #C4256B;
  --frame-ink-band: #6E665A;  /* drawn frames on ink: 3.4:1, a border not text */
  /* Raised panels on the ink bands. Opaque on purpose: they used to be
     translucent white, which let the band's graph run through the code. */
  --ink-raise:  #1B1A1A;
  --ink-raise-2: #1F1F1E;

  /* Text on the ink bands. Tinted from the paper hue, never grayed. */
  --on-ink:      #D9D2C6;  /* 12.7:1 */
  --on-ink-soft: #C3BAAB;  /*  9.9:1 */
  --on-ink-mute: #A79C8C;  /*  7.1:1 — the floor for body text here */

  --font-hand: 'Shantell Sans', 'Comic Sans MS', cursive;
  --font-body: 'Figtree', ui-sans-serif, system-ui, sans-serif;
  --font-code: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace;

  /* Type ramp. Nine steps; --t-5 is body. Anything not on this ramp is a bug —
     the first draft of this file drifted to 19 ad-hoc sizes, which is not a scale. */
  --t-1: 0.74rem;  --t-2: 0.82rem;   --t-3: 0.9rem;
  --t-4: 0.98rem;  --t-5: 1.0625rem; --t-6: 1.15rem;
  --t-7: 1.22rem;  --t-8: 1.35rem;   --t-9: 1.5rem;
  /* Three fluid display steps sit above the ramp. */
  --t-display: clamp(3.4rem, 11vw, 6rem);
  /* A two-sentence h1 needs a smaller step than three short words did. */
  --t-display-2: clamp(2.1rem, 4.6vw, 3rem);
  --t-section: clamp(2rem, 4.6vw, 3.1rem);
  --t-close:   clamp(2.2rem, 6vw, 3.8rem);

  --wrap: 76rem;
  --gutter: clamp(1.25rem, 4vw, 3rem);

  /* one spacing rhythm, used everywhere */
  --s1: 0.5rem;  --s2: 0.875rem; --s3: 1.5rem;
  --s4: 2.5rem;  --s5: 4rem;     --s6: 6.5rem;
}

/* ── The merge tree ──────────────────────────────────────────────────────
   A scribbled commit graph behind the page, parallaxing slower than the
   content and vanishing into the paper at both ends. Drawn by app.js with the
   same seeded wobble as the frames, so it reads as one hand.
   It is decoration: aria-hidden, pointer-events none, and thin enough at this
   opacity that body contrast is unaffected.                                  */
.tree-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0.26;
  /* the vanishing gradient — strongest mid-viewport, gone at both edges */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 22%, black 58%, transparent 95%);
          mask-image: linear-gradient(to bottom, transparent 0%, black 22%, black 58%, transparent 95%);
}
.tree-bg svg { display: block; width: 100%; }
.tree-bg path, .tree-bg circle, .tree-bg rect { vector-effect: non-scaling-stroke; }

/* Content sits above the tree. */
.masthead, main, .foot { position: relative; z-index: 1; }

/* An ink band's own background sits above the fixed paper layer and would
   always hide it, so each dark band carries its own graph instead. Same
   parallax, lane colours that read on dark, and the same vanishing fade —
   applied to a band-sized box so the graph dissolves at the band's edges
   rather than being cut off by them. */
.band--ink, .foot { position: relative; overflow: hidden; }
/* The footer is the close, so its graph is quieter than a band's. */
.foot > .tree-ink { opacity: 0.1; }
.foot > .wrap { position: relative; z-index: 1; }
.tree-ink {
  position: absolute; inset: 0;
  overflow: hidden; pointer-events: none;
  z-index: 0; opacity: 0.17;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 16%, black 72%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, black 16%, black 72%, transparent 100%);
}
.tree-ink__inner { position: absolute; left: 0; right: 0; }
.tree-ink svg { display: block; width: 100%; }
.band--ink > .wrap { position: relative; z-index: 1; }

/* Framed blocks take the paper ground so the graph passes behind them rather
   than through them — a commit line crossing a code sample is noise, not depth. */
.demo, .panel, .callout, .cost, .honest, .install__main, .install__side, .stamp {
  background-color: var(--paper);
}
.band--ink .honest { background-color: var(--ink-raise); }
.band--ink .stamp { background-color: var(--ink-raise); }
/* .install__side was missing from this override: it took the paper ground from
   the rule above while inheriting the ink band's light text, so the card
   rendered light-on-light — the exit descriptions washed out and the inline
   <code>advisory</code> disappeared entirely. .install__main has the same gap:
   its code block paints its own dark ground, so the paper only showed once the
   grid stretched it taller than the code — which giving the side its proper
   padding immediately did. */
.band--ink .install__side,
.band--ink .install__main { background-color: var(--ink-raise); }

/* ── Reset & base ────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background-color: var(--paper);
  /* paper fibre — a static grain, generated once, no request */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23f)' opacity='0.055'/%3E%3C/svg%3E");
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: var(--t-5);
  line-height: 1.65;
  font-variant-ligatures: none;
}

h1, h2, h3 {
  font-family: var(--font-hand);
  color: var(--ink);
  line-height: 1.04;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 0;
}

p { margin: 0 0 var(--s3); max-width: 68ch; }
a { color: inherit; }

code, pre { font-family: var(--font-code); font-variant-ligatures: none; }
code { font-size: 0.9em; }

:focus-visible {
  outline: 3px solid var(--pink-deep);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Clipped rather than pushed off-canvas — a -9999px box still widens the document. */
.skip-link {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
  background: var(--ink); color: var(--paper);
  padding: var(--s2) var(--s3); z-index: 100;
  font-family: var(--font-hand); font-weight: 700;
}
.skip-link:focus {
  width: auto; height: auto; overflow: visible; clip-path: none;
  left: var(--s3); top: var(--s3);
}

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); }
.wrap--narrow { max-width: 46rem; }

/* Grid children default to min-width:auto, so any <pre> inside forces the track
   to max-content and the whole document overflows. This is the fix. */
.hero > *, .demo__prs > *, .panels > *, .split > *,
.install > *, .cost__facts > *, .foot__grid > * { min-width: 0; }

/* ── Hand-drawn frame plumbing ───────────────────────────────────────────
   app.js injects <svg class="frame-svg"> as the first child of [data-frame].
   The element itself carries no border — the SVG is the border.            */
/* The transparent border reserves the drawn frame's geometry so nothing shifts;
   without JS there is no SVG to draw, so the same border simply becomes visible. */
[data-frame] {
  position: relative;
  border: 2.5px solid transparent;
  border-radius: 14px;
}
html:not(.js) [data-frame] { border-color: var(--ink); }
html:not(.js) [data-frame-shape="circle"] { border-radius: 50%; }
html:not(.js) .band--ink [data-frame] { border-color: var(--frame-ink-band); }
.frame-svg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 0;
}
/* `:not(.frame-fill)` because a presentation attribute loses to any authored
   rule: `drawFrame()` sets fill="…" on the fill path, but a blanket
   `.frame-svg path { fill: none }` overrides it, so every data-frame-fill frame
   painted nothing. Labels inside filled frames are wrapped in elements so the
   `z-index: 1` child rule below lifts them over the fill — a bare text node
   cannot be lifted, and would sit behind it. */
.frame-svg path:not(.frame-fill) { fill: none; stroke: var(--ink); stroke-linecap: round; stroke-linejoin: round; }
.frame-svg .frame-fill { stroke: none; }
[data-frame] > *:not(.frame-svg) { position: relative; z-index: 1; }

/* ── Highlighter swipes ──────────────────────────────────────────────────
   A background mark rather than an absolutely-positioned child: an inline span
   that wraps produces two fragments, and only a background paints both.      */
[data-highlight] {
  background-repeat: no-repeat;
  background-position: 0 88%;
  background-size: 0% 0.94em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  padding-inline: 0.12em;
}
[data-highlight].is-swiped { background-size: 104% 0.94em; }
/* No JS means no .is-swiped, so the mark is simply painted from the start. */
html:not(.js) [data-highlight] { background-size: 104% 0.94em; }
[data-highlight="yellow"] {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 22' preserveAspectRatio='none'%3E%3Cpath d='M1 16 C22 5,58 7,99 4.5 L99 17 C64 20.5,28 18,1 19.5 Z' fill='%23FFE34D'/%3E%3C/svg%3E");
}
[data-highlight="pink"] {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 22' preserveAspectRatio='none'%3E%3Cpath d='M1 16 C22 5,58 7,99 4.5 L99 17 C64 20.5,28 18,1 19.5 Z' fill='%23FF6BAE'/%3E%3C/svg%3E");
}
[data-highlight="teal"] {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 22' preserveAspectRatio='none'%3E%3Cpath d='M1 16 C22 5,58 7,99 4.5 L99 17 C64 20.5,28 18,1 19.5 Z' fill='%2300C7B7'/%3E%3C/svg%3E");
}

/* On paper the mark can sit low like a real highlighter, because the ink stays
   readable either way. On the ink bands the text must flip to dark to read against
   the mark, which means the mark has to cover the whole glyph — a low swipe would
   leave the ascenders as dark-on-dark. */
/* A filled marker is a PAPER device. On an ink band it forces the reader to flip
   polarity mid-sentence — dark text in a bright slab inside light-on-black copy —
   and the sentence stops being readable however well the mark covers the glyphs.
   On ink, the same emphasis is carried by colouring the text and underlining it
   with a thin swipe in the same ink. Measured: teal on #111 is 8.9:1. */
.band--ink [data-highlight] {
  color: var(--teal);
  font-weight: 600;
  /* Overshoot both ends and let the stroke lift toward the right, the way a
     hand pulls a marker past the last letter. Same instinct as the frames,
     which never close exactly where they started. */
  /* Asymmetric on purpose: a small lead-in, then a long pull past the last
     letter. 0.22em was measurably there and visually invisible — an overshoot
     has to be a gesture, not a rounding error. */
  background-position: -0.3em 100%;
  background-size: calc(100% + 1.25em) 0.22em;
  padding-inline: 0;
}
.band--ink [data-highlight].is-swiped { background-size: calc(100% + 1.25em) 0.22em; }
html:not(.js) .band--ink [data-highlight] { background-size: calc(100% + 1.25em) 0.22em; }

/* A drawn underline: thicker at the start, lifting and tapering off the end. */
.band--ink [data-highlight="teal"] {
  color: var(--teal);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath d='M0 6.2 C18 3.4,46 4.0,72 2.6 C85 1.9,93 1.1,100 0 L100 2.6 C93 4.2,85 5.2,72 6.1 C46 7.9,18 7.4,0 9.6 Z' fill='%2300C7B7'/%3E%3C/svg%3E");
}
.band--ink [data-highlight="yellow"] {
  color: var(--yellow);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath d='M0 6.2 C18 3.4,46 4.0,72 2.6 C85 1.9,93 1.1,100 0 L100 2.6 C93 4.2,85 5.2,72 6.1 C46 7.9,18 7.4,0 9.6 Z' fill='%23FFE34D'/%3E%3C/svg%3E");
}
.band--ink [data-highlight="pink"] {
  color: var(--pink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath d='M0 6.2 C18 3.4,46 4.0,72 2.6 C85 1.9,93 1.1,100 0 L100 2.6 C93 4.2,85 5.2,72 6.1 C46 7.9,18 7.4,0 9.6 Z' fill='%23FF6BAE'/%3E%3C/svg%3E");
}

.ink-teal { color: var(--teal-deep); }
.ink-pink { color: var(--pink-deep); }

/* ── Masthead ────────────────────────────────────────────────────────────── */
.masthead {
  display: flex; align-items: center; gap: var(--s3);
  max-width: var(--wrap); margin-inline: auto;
  padding: var(--s3) var(--gutter);
}
.wordmark { display: inline-flex; align-items: center; gap: 0.55rem; text-decoration: none; }
.wordmark__mark { width: 2rem; height: 2rem; flex: none; }
.wordmark__mark path {
  fill: none; stroke: var(--ink); stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.wordmark__text {
  font-family: var(--font-hand); font-weight: 700;
  font-size: var(--t-9); color: var(--ink); letter-spacing: -0.03em;
}
.masthead__nav { display: flex; gap: var(--s3); margin-left: auto; }
.masthead__nav a {
  font-family: var(--font-hand); font-weight: 600; font-size: var(--t-5);
  color: var(--ink-soft); text-decoration: none; padding-bottom: 2px;
  background-image: linear-gradient(var(--teal), var(--teal));
  background-size: 0% 3px; background-repeat: no-repeat; background-position: 0 100%;
  transition: background-size 180ms cubic-bezier(0.2, 0.8, 0.3, 1), color 180ms;
}
.masthead__nav a:hover { color: var(--ink); background-size: 100% 3px; }

@media (max-width: 62rem) {
  .masthead__nav { display: none; }
  .masthead { gap: var(--s2); }
  .masthead .btn--sm { margin-left: auto; }
}

/* ── Buttons ─────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-family: var(--font-hand); font-weight: 700;
  color: var(--ink); text-decoration: none; text-align: center;
  padding: 0.7rem 1.4rem;
  transition: transform 160ms cubic-bezier(0.2, 0.8, 0.3, 1);
}
.btn--sm { font-size: var(--t-4); padding: 0.45rem 1rem; }
.btn--lg { font-size: var(--t-7); padding: 0.9rem 1.8rem; }
.btn:hover { transform: translate(-1px, -2px) rotate(-0.5deg); }
.btn:active { transform: translate(0, 1px) rotate(0deg); }
.btn__arrow { width: 2rem; height: 0.9rem; flex: none; }
.btn__arrow path { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* impact ticks on hover — the zine's own "active" state */
.btn::after {
  content: ""; position: absolute; inset: -10px; z-index: 2;
  background-image:
    linear-gradient(var(--ink), var(--ink)), linear-gradient(var(--ink), var(--ink)),
    linear-gradient(var(--ink), var(--ink)), linear-gradient(var(--ink), var(--ink));
  background-repeat: no-repeat;
  background-size: 9px 2px, 9px 2px, 2px 9px, 2px 9px;
  background-position: -2px 30%, calc(100% + 2px) 70%, 22% -2px, 78% calc(100% + 2px);
  opacity: 0; transition: opacity 160ms, background-position 200ms cubic-bezier(0.2, 0.8, 0.3, 1);
  pointer-events: none;
}
.btn:hover::after {
  opacity: 1;
  background-position: -7px 30%, calc(100% + 7px) 70%, 22% -7px, 78% calc(100% + 7px);
}
@media (prefers-reduced-motion: reduce) {
  .btn:hover { transform: none; }
  .btn::after { display: none; }
}

/* ── Hero ────────────────────────────────────────────────────────────────── */
.hero {
  max-width: var(--wrap); margin-inline: auto;
  padding: var(--s4) var(--gutter) var(--s5);
  display: grid; gap: var(--s5);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 64rem) {
  .hero { grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr); gap: var(--s4); align-items: start; }
}

.hero__title {
  font-size: var(--t-display);
  font-weight: 800; letter-spacing: -0.045em;
  margin-bottom: var(--s3);
}
.hero__title span { display: block; }
/* Two long lines rather than three short words, so it needs a smaller step. */
.hero__title--two { font-size: var(--t-display-2); letter-spacing: -0.035em; }
.hero__lede {
  font-size: clamp(var(--t-6), 1.6vw, var(--t-8));
  color: var(--ink-soft); max-width: 34ch; margin-bottom: var(--s4);
}
.hero__lede code { font-size: 0.88em; font-weight: 700; }

.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); margin-bottom: var(--s4); }
.hero__actions--center { justify-content: center; }

.hero__checks { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.hero__checks li {
  display: flex; align-items: center; gap: 0.6rem;
  font-family: var(--font-hand); font-weight: 600; font-size: var(--t-4);
  color: var(--ink-soft);
}
.tick { width: 1.15rem; height: 1.15rem; flex: none; }
.tick path { fill: none; stroke: var(--teal-deep); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }

/* ── The demo ────────────────────────────────────────────────────────────── */
.demo { margin: 0; padding: var(--s3); background-color: var(--paper); }
.demo__cap { margin-bottom: var(--s3); }
.demo__cap-title {
  font-family: var(--font-hand); font-weight: 700; font-size: var(--t-5); color: var(--ink);
}
.demo__prs { display: grid; gap: var(--s2); margin-bottom: var(--s3); }
@media (min-width: 30rem) {
  .demo__prs { grid-template-columns: 1fr 1fr; grid-template-rows: auto auto auto; }
  /* Subgrid so a wrapped title in one card cannot knock the other card's code
     block and verdict out of alignment. */
  @supports (grid-template-rows: subgrid) {
    .pr { display: grid; grid-row: span 3; grid-template-rows: subgrid; gap: 0; }
  }
}

.pr { transition: opacity 220ms ease-out, filter 220ms ease-out; }
.pr__head { display: flex; flex-direction: column; gap: 0.15rem; margin-bottom: var(--s1); }
.pr__id {
  font-family: var(--font-hand); font-weight: 800; font-size: var(--t-5); color: var(--ink);
}
.pr__title { font-size: var(--t-3); line-height: 1.4; color: var(--ink-mute); }
.pr__title code { font-size: 0.95em; }
.pr__state {
  margin: var(--s1) 0 0; font-family: var(--font-hand); font-weight: 700; font-size: var(--t-3);
  color: var(--teal-deep);
}
.pr[data-dim="true"] { opacity: 0.32; filter: saturate(0.2); }

.demo__controls { display: flex; gap: 0; margin-bottom: var(--s3); flex-wrap: wrap; }
.seg {
  flex: 1 1 auto; min-width: 6.5rem;
  font-family: var(--font-hand); font-weight: 700; font-size: var(--t-4);
  background: transparent; color: var(--ink-mute);
  border: 2.5px solid var(--ink); border-right-width: 0;
  padding: 0.5rem 0.5rem; cursor: pointer;
  transition: background-color 160ms, color 160ms;
}
.seg:first-child { border-radius: 14px 4px 4px 14px; }
.seg:last-child  { border-radius: 4px 14px 14px 4px; border-right-width: 2.5px; }
.seg:hover { background: rgba(0,0,0,0.045); color: var(--ink); }
.seg[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

.demo__result { display: block; }
.demo__verdict { display: flex; align-items: center; gap: 0.5rem; margin-bottom: var(--s2); }
.verdict-icon { width: 1.5rem; height: 1.5rem; flex: none; display: none; }
.verdict-icon path { fill: none; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.verdict-icon[data-icon="cross"] path { stroke: var(--pink-deep); }
.verdict-icon[data-icon="tick"]  path { stroke: var(--teal-deep); }
[data-result="fail"] .verdict-icon[data-icon="cross"] { display: block; }
[data-result="pass"] .verdict-icon[data-icon="tick"]  { display: block; }
.demo__verdict-text { font-family: var(--font-hand); font-weight: 800; font-size: var(--t-7); }
[data-result="fail"] .demo__verdict-text { color: var(--pink-deep); }
[data-result="pass"] .demo__verdict-text { color: var(--teal-deep); }

.demo__note {
  margin: var(--s2) 0 0; font-family: var(--font-hand); font-weight: 600;
  font-size: var(--t-3); color: var(--ink-mute); max-width: none;
}

/* ── Code ────────────────────────────────────────────────────────────────── */
.code {
  font-size: var(--t-2); line-height: 1.62;
  background: var(--paper-2);
  color: var(--ink);
  padding: var(--s2) var(--s3);
  margin: 0;
  border-radius: 3px;
  overflow-x: auto;
  white-space: pre;
  tab-size: 2;
  max-width: 100%;
}
/* Scrollable regions must be reachable by keyboard (app.js adds tabindex). */
.code:focus-visible { outline: 3px solid var(--pink-deep); outline-offset: 2px; }
.code--sm { font-size: var(--t-1); padding: 0.6rem 0.75rem; }
.code--out { font-size: var(--t-2); }
.code--file { font-size: var(--t-2); padding: var(--s3); }
.band--ink .code { background: var(--ink-raise-2); color: var(--paper); }
.band--ink .code--out { background: var(--ink-raise-2); }

.c-path { color: var(--ink-mute); }
.band--ink .c-path { color: var(--on-ink-mute); }
.c-dim  { color: var(--ink-mute); }
.c-cmt  { color: var(--ink-mute); font-style: italic; }
.band--ink .c-cmt { color: var(--on-ink-mute); }
.c-add  { color: var(--teal-deep); }
.band--ink .c-add { color: var(--teal); }
.c-del  { color: var(--pink-deep); }
.band--ink .c-del { color: var(--pink); }
.c-err  { color: var(--pink-deep); font-weight: 700; }
.band--ink .c-err { color: var(--pink); }
.c-key  { color: var(--ink); font-weight: 700; }
.band--ink .c-key { color: var(--paper); }
.c-str  { color: var(--teal-deep); }
.band--ink .c-str { color: var(--teal); }
.c-hot  { background: var(--yellow); color: var(--ink); font-weight: 700; padding: 0 3px; border-radius: 2px; }
.c-hit  { background: rgba(255, 107, 174, 0.22); color: inherit; font-weight: 700; }
.band--ink .c-hit { background: rgba(255, 107, 174, 0.24); }

/* ── Bands & section furniture ───────────────────────────────────────────── */
.band { padding-block: var(--s6); }
.band--tight { padding-block: var(--s5); }
.band--paper + .band--paper { padding-top: 0; }
.band--ink {
  background: var(--ink);
  color: var(--on-ink);
}
.band--ink h2, .band--ink h3, .band--ink strong { color: var(--paper); }
.band--ink .sect-lede { color: var(--on-ink-soft); }
.band--ink code { color: var(--paper); }
.band--ink .frame-svg path { stroke: var(--frame-ink-band); }

.sect-title {
  font-size: var(--t-section);
  font-weight: 800; letter-spacing: -0.035em;
  margin-bottom: var(--s3);
  max-width: 20ch;
}
.sect-lede {
  font-size: var(--t-6); color: var(--ink-soft);
  max-width: 62ch; margin-bottom: var(--s5);
}

.aside {
  font-family: var(--font-hand); font-weight: 600;
  font-size: var(--t-5); line-height: 1.55;
  color: var(--ink-mute); max-width: 52ch;
  margin-top: var(--s4);
  padding-left: var(--s3);
}
.band--ink .aside { color: var(--on-ink-mute); }
.aside[data-annotate="left"] { position: relative; }

/* ── Numbered panels ─────────────────────────────────────────────────────── */
.panels {
  list-style: none; margin: 0 0 var(--s5); padding: 0;
  display: grid; gap: var(--s4);
}
@media (min-width: 52rem) { .panels { grid-template-columns: repeat(3, 1fr); gap: var(--s3); } }

.panel { padding: var(--s3); padding-top: var(--s4); }
.panel__n {
  position: absolute; top: -0.95rem; left: var(--s3);
  width: 2.1rem; height: 2.1rem;
  display: grid; place-items: center;
  font-family: var(--font-hand); font-weight: 800; font-size: var(--t-5);
  color: var(--ink); background: var(--paper);
  z-index: 2;
}
.panel__title { font-size: var(--t-7); font-weight: 700; margin-bottom: var(--s1); }
.panel p { font-size: var(--t-4); margin-bottom: var(--s2); }
.panel--alarm .panel__title { color: var(--pink-deep); }

/* ── Callout ─────────────────────────────────────────────────────────────── */
.callout { padding: var(--s4) var(--s3) var(--s3); }
.callout__title { font-size: var(--t-9); font-weight: 700; margin-bottom: var(--s2); }
.callout__body { font-size: var(--t-5); margin-bottom: var(--s3); }
.callout__foot {
  font-family: var(--font-hand); font-weight: 600; font-size: var(--t-4);
  color: var(--ink-mute); margin: var(--s3) 0 0; max-width: none;
}

/* ── Blast radius ────────────────────────────────────────────────────────
   A component graph you can poke: pick the changed component, see what has to
   be rebuilt. Nodes are real buttons; edges are drawn between them at runtime
   from the same scribble the tree and the frames use.                       */
.blast { margin: 0 0 var(--s4); padding: var(--s3); background-color: var(--paper); }
.blast__cap { display: flex; flex-wrap: wrap; gap: 0.4rem var(--s3); align-items: baseline; margin-bottom: var(--s3); }
.blast__cap-title { font-family: var(--font-hand); font-weight: 700; font-size: var(--t-5); color: var(--ink); }
.blast__cap-note {
  font-family: var(--font-hand); font-weight: 600; font-size: var(--t-3); color: var(--ink-mute);
}

/* The graph has a hard min-width, so below ~48rem it scrolls inside its own
   box rather than widening the document. Say so — a silently clipped diagram
   reads as a broken one. */
.blast__cap-scroll { display: none; }
@media (max-width: 48rem) {
  .blast__cap-scroll {
    display: block;
    font-family: var(--font-hand); font-weight: 700; font-size: var(--t-3);
    color: var(--pink-deep);
  }
}
.blast__scroll { overflow-x: auto; overflow-y: hidden; padding: var(--s2) 0; }
.blast__scroll:focus-visible { outline: 3px solid var(--pink-deep); outline-offset: 2px; }

.graph {
  position: relative;
  display: grid;
  /* Six layers, one per depth in the derived graph: [store crypto] auth session
     billing api web. The count is load-bearing — a wrong column count silently
     wraps the last component onto a second row, and the edge lines then draw
     backwards across the figure. */
  grid-template-columns: repeat(6, minmax(6.5rem, 1fr));
  gap: var(--s3) var(--s5);
  align-items: center;
  min-width: 54rem;
}
.graph__edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; }
.graph__edges path { fill: none; stroke-linecap: round; }
.graph__col { display: grid; gap: var(--s3); position: relative; z-index: 1; }

.node {
  font-family: var(--font-code); font-size: var(--t-3); font-weight: 700;
  color: var(--ink); background-color: var(--paper);
  border: 2.5px solid var(--ink);
  border-radius: 10px;
  padding: 0.55rem 0.4rem;
  cursor: pointer;
  transition: background-color 180ms, color 180ms, border-color 180ms, transform 180ms cubic-bezier(0.2, 0.8, 0.3, 1);
}
.node:hover { transform: translate(-1px, -2px) rotate(-0.6deg); }
.node[data-state="changed"] { background-color: var(--yellow); border-color: var(--ink); }
.node[data-state="hit"]     { background-color: var(--pink); border-color: var(--ink); }
.node[data-state="safe"]    { color: var(--ink-mute); border-color: var(--ink-mute); background-color: transparent; }
@media (prefers-reduced-motion: reduce) { .node:hover { transform: none; } }

.blast__out { display: block; margin-top: var(--s3); }
.blast__verdict {
  font-family: var(--font-hand); font-weight: 700; font-size: var(--t-6);
  color: var(--ink); margin: 0 0 var(--s2); max-width: none;
}
.blast__verdict b { color: var(--pink-deep); }
.blast__verdict i { font-style: normal; color: var(--teal-deep); }

.blast__key {
  list-style: none; margin: var(--s3) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3);
}
.blast__key li {
  display: flex; align-items: center; gap: 0.45rem;
  font-family: var(--font-hand); font-weight: 600; font-size: var(--t-3); color: var(--ink-mute);
}
.key-swatch { width: 1rem; height: 1rem; border: 2.5px solid var(--ink); border-radius: 4px; flex: none; }
.key-swatch--changed { background-color: var(--yellow); }
.key-swatch--hit { background-color: var(--pink); }
.key-swatch--safe { background-color: transparent; border-color: var(--ink-mute); }

/* ── Note ────────────────────────────────────────────────────────────────
   Names a capability that is partly shipped and partly roadmap, so the two
   states are set as a definition list and never blur into one claim.       */
.note {
  margin-top: var(--s4);
  padding: var(--s4) var(--s3) var(--s3);
  background-color: var(--paper);
}
.note__title { font-size: var(--t-8); font-weight: 700; margin-bottom: var(--s2); }
.note p { font-size: var(--t-4); }
.note__status {
  margin: 0; padding-top: var(--s2);
  border-top: 2px dashed rgba(17, 17, 17, 0.18);
  display: grid; gap: var(--s1) var(--s3);
}
@media (min-width: 44rem) { .note__status { grid-template-columns: max-content 1fr; } }
.note__after { font-size: var(--t-4); margin: var(--s3) 0 0; }

/* ── Proof (the gate, run on its own source) ─────────────────────────────── */
/* Sits at the close of the free-gate band, which is `band--ink`, so it raises
   off the ink the same way .honest and .stamp do rather than inventing a
   surface. It is the one place on the page where the product is shown working
   on code the reader can go and read, so it gets the width of the band and is
   not squeezed into a split. */
.proof {
  margin-top: var(--s4);
  padding: var(--s4) var(--s3) var(--s3);
  max-width: 62rem;
}
.band--ink .proof { background-color: var(--ink-raise); }
.proof__head { font-size: var(--t-8); font-weight: 700; margin-bottom: var(--s2); }
.proof__lede { font-size: var(--t-4); line-height: 1.6; margin-bottom: var(--s3); }
.proof__after { font-size: var(--t-4); line-height: 1.6; margin: var(--s3) 0 0; }
.proof__repro { font-size: var(--t-5); margin: var(--s2) 0 0; }
.band--ink .proof__lede,
.band--ink .proof__after { color: var(--on-ink-soft); }
.band--ink .proof__repro { color: var(--on-ink-mute); }
.note__status dt {
  font-family: var(--font-hand); font-weight: 700; font-size: var(--t-3);
  color: var(--ink); white-space: nowrap;
}
.note__status dt:first-of-type { color: var(--teal-deep); }
.note__status dt:last-of-type { color: var(--pink-deep); }
.note__status dd {
  margin: 0 0 var(--s2); font-size: var(--t-3); line-height: 1.55; color: var(--ink-mute);
}
@media (min-width: 44rem) { .note__status dd { margin-bottom: 0; } }

/* ── Split (gate section) ────────────────────────────────────────────────── */
.split { display: grid; gap: var(--s4); }
@media (min-width: 56rem) { .split { grid-template-columns: 1fr 1fr; gap: var(--s4); align-items: start; } }
.split__title { font-size: var(--t-8); font-weight: 700; margin-bottom: var(--s2); }
.split__body { font-size: var(--t-5); margin-bottom: var(--s3); }
.split__foot {
  font-family: var(--font-hand); font-weight: 600; font-size: var(--t-3);
  color: var(--ink-mute); margin: var(--s2) 0 0;
}
.band--ink .split__foot { color: var(--on-ink-mute); }
.split .code { padding: var(--s3); }

/* ── Honest list ─────────────────────────────────────────────────────────── */
.honest { margin-top: var(--s5); padding: var(--s4) var(--s3) var(--s3); }
.band--ink .honest { background-color: var(--ink-raise); }
.honest__title { font-size: var(--t-8); font-weight: 700; margin-bottom: var(--s3); }
.honest__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.honest__list li {
  font-size: var(--t-4); line-height: 1.6; padding-left: 1.6rem; position: relative; max-width: 62ch;
}
.honest__list li::before {
  content: ""; position: absolute; left: 0; top: 0.62em;
  width: 0.75rem; height: 2px; background: var(--pink);
  border-radius: 2px;
}

/* ── Install ─────────────────────────────────────────────────────────────── */
.install { display: grid; gap: var(--s4); }
@media (min-width: 60rem) { .install { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); } }
.install__main .code { white-space: pre; }
/* Matches .honest: the framed blocks on this page carry their own padding, and
   without it the copy ran to the card's drawn edge and past it. */
.install__side { padding: var(--s4) var(--s3) var(--s3); }
.side__title { font-family: var(--font-hand); font-size: var(--t-7); font-weight: 700; margin-bottom: var(--s3); }
.exits { margin: 0 0 var(--s3); display: grid; gap: var(--s2); }
.exits > div { display: flex; align-items: flex-start; gap: 0.75rem; }
.exits dt {
  width: 2.1rem; height: 2.1rem; flex: none;
  display: grid; place-items: center;
  font-family: var(--font-code); font-weight: 700; font-size: var(--t-4); color: var(--ink);
}
.exits dd { margin: 0; font-size: var(--t-4); line-height: 1.5; padding-top: 0.3rem; }
.side__note { font-size: var(--t-4); color: var(--ink-mute); }
.band--ink .side__note { color: var(--on-ink-mute); }

/* ── The suite (three products, stated once, early) ──────────────────────── */
/* Not a card row of icon+heading+text: no icons, and each item is a drawn frame
   carrying a name, what it costs, and what it does. The reader needs the map
   before the evidence, and the map is three lines long. */
.suite {
  list-style: none; margin: 0 0 var(--s3); padding: 0;
  display: grid; gap: var(--s2);
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 900px) { .suite { grid-template-columns: minmax(0, 1fr); } }

.suite__item {
  position: relative;
  display: grid; gap: 0.4rem; align-content: start;
  padding: var(--s3) var(--s2) var(--s2);
}
.suite__name {
  font-family: var(--font-hand); font-weight: 800; font-size: var(--t-8);
  line-height: 1.1; color: var(--ink);
}
.suite__price {
  font-family: var(--font-code); font-size: var(--t-1);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-mute);
}
.suite__what { font-size: var(--t-4); line-height: 1.6; color: var(--ink-soft); }

/* ── The field (one axis, named vendors) ─────────────────────────────────── */
/* Two columns, not a matrix. The claim is a single capability, so the layout
   is a single comparison — the yellow fill marks which side the product is on,
   and the caveat below carries the review date. */
.field {
  display: grid; gap: var(--s2); margin-bottom: var(--s3);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: stretch;
}
@media (max-width: 820px) { .field { grid-template-columns: minmax(0, 1fr); } }

.field__col {
  position: relative;
  display: grid; gap: 0.55rem; align-content: start;
  padding: var(--s3) var(--s3) var(--s2);
}
.field__title {
  margin: 0;
  font-family: var(--font-hand); font-weight: 800; font-size: var(--t-8);
  line-height: 1.12; color: var(--ink);
}
.field__who {
  margin: 0;
  font-family: var(--font-code); font-size: var(--t-2); line-height: 1.7;
  color: var(--ink-mute);
}
.field__col--us .field__who {
  color: var(--ink); font-weight: 700;
}
.field__note { margin: 0; font-size: var(--t-4); line-height: 1.55; color: var(--ink-soft); }

.field__caveat {
  font-size: var(--t-4); line-height: 1.6; color: var(--ink-mute);
  max-width: 72ch;
}

/* ── OSS / paid matrix ───────────────────────────────────────────────────── */
/* The outer container carries the drawn frame, per the system's rule that
   framed blocks are drawn rather than bordered. Inside it a real table earns
   hairlines: a capability grid needs row alignment more than it needs wobble,
   the same trade already recorded for .seg. */
.matrix__head {
  margin: var(--s5) 0 var(--s2);
  font-family: var(--font-hand); font-weight: 800; font-size: var(--t-8);
}
.matrix__lede { font-size: var(--t-4); margin-bottom: var(--s3); max-width: 66ch; }
.band--ink .matrix__lede { color: var(--on-ink-soft); }

.matrix { padding: var(--s3) var(--s2) var(--s2); overflow-x: auto; }
.band--ink .matrix { background-color: var(--ink-raise); }
.matrix table { width: 100%; border-collapse: collapse; min-width: 44rem; }
.matrix__cap {
  caption-side: top; text-align: left; padding: 0 0 var(--s2);
  font-size: var(--t-2); color: var(--ink-mute);
}
.band--ink .matrix__cap { color: var(--on-ink-mute); }

.matrix th, .matrix td {
  text-align: left; padding: 0.6rem 0.7rem; vertical-align: top;
  font-size: var(--t-3); line-height: 1.45;
}
.matrix thead th {
  font-family: var(--font-hand); font-weight: 800; font-size: var(--t-4);
  border-bottom: 2px solid var(--frame-ink-band);
}
.matrix__meta {
  display: block; margin-top: 0.15rem;
  font-family: var(--font-code); font-weight: 400; font-size: var(--t-1);
  letter-spacing: 0.03em; text-transform: uppercase; color: var(--on-ink-mute);
}
.matrix tbody th { font-weight: 400; max-width: 24rem; }
.matrix tbody tr + tr th, .matrix tbody tr + tr td {
  border-top: 1px solid var(--frame-ink-band);
}
.matrix td { white-space: nowrap; }

/* Status is never colour alone: each marker says the word. */
.mk { font-family: var(--font-code); font-size: var(--t-1); letter-spacing: 0.03em; }
/* Teal is a highlighter here, not an ink — DESIGN.md forbids body text in raw
   teal or pink. "Included" carries its own meaning, so it takes the normal ink;
   yellow attends, which is exactly what "on the way" is asking the eye to do. */
.mk--yes { color: var(--on-ink); }
.mk--soon { color: var(--highlight-yellow); }
.mk--na { color: var(--on-ink-mute); }

.matrix__foot {
  margin-top: var(--s3); font-size: var(--t-4); line-height: 1.6; max-width: 70ch;
}
.band--ink .matrix__foot { color: var(--on-ink-soft); }

/* ── Cost ────────────────────────────────────────────────────────────────── */
.cost { padding: var(--s4) var(--s3) var(--s3); }
.cost__bar {
  display: flex; width: 100%; height: 3.5rem; margin-bottom: var(--s3);
  border-radius: 4px; overflow: hidden;
}
.cost__seg { display: grid; place-items: center; overflow: hidden; }
.cost__seg span {
  font-family: var(--font-hand); font-weight: 700; font-size: var(--t-3);
  white-space: nowrap; padding-inline: 0.5rem;
}
.cost__seg--engine { width: 92%; background: var(--yellow); color: var(--ink); }
/* No label inside this one: at 8% of the bar the word is wider than the box and
   simply gets sliced. The caption under the bar names it instead. */
.cost__seg--orch   { width: 8%;  background: var(--ink); }
.cost__barnote {
  font-family: var(--font-hand); font-weight: 600; font-size: var(--t-3);
  color: var(--ink-mute); margin: calc(var(--s3) * -1 + 0.35rem) 0 var(--s3); max-width: none;
}
.cost__facts { display: grid; gap: var(--s2); margin-bottom: var(--s3); }
@media (min-width: 48rem) { .cost__facts { grid-template-columns: repeat(3, 1fr); gap: var(--s3); } }
.cost__facts p { margin: 0; font-size: var(--t-4); max-width: none; }
.cost__facts strong { font-family: var(--font-hand); font-size: var(--t-8); display: block; color: var(--ink); }
.cost__caveat {
  font-size: var(--t-3); line-height: 1.55; color: var(--ink-mute);
  margin: 0; padding-top: var(--s2); max-width: none;
  border-top: 2px dashed rgba(17, 17, 17, 0.18);
}

/* ── Stamp ───────────────────────────────────────────────────────────────── */
.stamp {
  display: block; max-width: 46rem;
  padding: var(--s3);
  font-family: var(--font-hand); font-weight: 600; font-size: var(--t-5); line-height: 1.55;
  color: var(--on-ink-soft);
}
.stamp strong { color: var(--pink); }

/* The address, spelled out under the button. A bare mailto: button hides where
   it goes, and someone deciding whether to write to a stranger about a
   partnership should be able to read the address before a mail client opens on
   top of the page — which is exactly what the button does on a phone. */
.stamp__addr {
  max-width: 46rem;
  margin-top: calc(var(--s3) * -1);
  font-size: var(--t-5); line-height: 1.6;
  color: var(--ink-mute);
}
.band--ink .stamp__addr { color: var(--on-ink-mute); }

/* ── Close ───────────────────────────────────────────────────────────────── */
.close { padding-block: var(--s6); text-align: center; }
.close__title { font-size: var(--t-close); font-weight: 800; letter-spacing: -0.04em; margin-bottom: var(--s3); }
.close__lede { font-size: var(--t-6); margin-inline: auto; margin-bottom: var(--s3); }
.close__cmd {
  display: inline-block; text-align: left;
  margin: 0 auto var(--s4); padding: var(--s2) var(--s3);
  background-color: var(--paper);
}
.close__foot {
  font-size: var(--t-3); color: var(--ink-mute); margin-inline: auto; margin-top: var(--s5);
  max-width: 56ch;
}

/* ── Footer ──────────────────────────────────────────────────────────────── */
.foot { background: var(--ink); color: var(--on-ink-mute); padding-block: var(--s5) var(--s4); }
.foot__grid { display: grid; gap: var(--s4); margin-bottom: var(--s4); }
@media (min-width: 48rem) { .foot__grid { grid-template-columns: 1.4fr 1fr 1fr; } }
.foot .wordmark__text { color: var(--paper); }
.foot__brand p { font-size: var(--t-4); margin: var(--s1) 0 0; max-width: 30ch; }
.foot__col h3 { font-family: var(--font-hand); font-size: var(--t-5); color: var(--paper); margin-bottom: var(--s1); }
.foot__col p { font-size: var(--t-3); margin: 0 0 0.35rem; }
.foot__col a { font-size: var(--t-3); color: var(--teal); text-decoration: none; overflow-wrap: anywhere; }
.foot__col a:hover { text-decoration: underline; }
/* #6E665A measured 3.36:1 on #111 — below the 4.5:1 floor. #9A9083 is 6.1:1. */
.foot__muted { font-size: var(--t-3); color: var(--on-ink-mute); }
.foot__legal {
  font-size: var(--t-2); color: var(--on-ink-mute); padding-top: var(--s3); margin: 0;
  border-top: 1px solid rgba(255, 248, 236, 0.12); max-width: var(--wrap);
}

/* ── Motion: one authored moment — the frames draw themselves in ──────────
   Frames are visible by DEFAULT. Only an element JS has explicitly queued for
   the draw-on gets hidden, so a frame is never invisible because an observer
   failed to fire, JS died, or the page was printed.                          */
@media (prefers-reduced-motion: no-preference) {
  .frame-svg.is-pending path {
    stroke-dasharray: var(--len, 0);
    stroke-dashoffset: var(--len, 0);
  }
  .frame-svg.is-drawn path {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 620ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  .frame-svg.is-drawn path:nth-of-type(2) { transition-delay: 90ms; }
  [data-highlight] {
    transition: background-size 480ms cubic-bezier(0.16, 1, 0.3, 1) 120ms;
  }
}
/* Without the animation the mark is simply always there. */
@media (prefers-reduced-motion: reduce) {
  [data-highlight] { background-size: 104% 0.94em; }
}
