/* ==========================================================================
   headroom landing page

   Design system: DESIGN-SYSTEM.md. Built 2026-08-16 from herdr.dev, measured
   in both of its modes at 1440px. Nothing may introduce a colour, a size or a
   radius that is not declared there.

   The page is a sheet laid on drafting paper. The paper is a 72px grid painted
   on the body; the sheet is a 1160px column painted over it with a hairline
   down each side. Two modes, ink and paper, both painted explicitly, so a
   viewer whose system asks for the other one still gets the sheet as chosen.

   Radius is zero everywhere. Sans is Archivo, and it is only ever the headline
   or the running copy; every label, control, badge and caption is JetBrains
   Mono, uppercase, tracked open. Both faces are served from this origin.

   Exactly one hue exists in this file and it only ever marks the connections
   that have nowhere to go. Severity keeps its exception, because severity is
   what the CLI prints and not a decoration. If a second brand colour ever
   appears here, the first one has stopped meaning anything.

   Superseded: paper and ink at weight 300 (2026-08-15), and electric cyan on
   midnight before that (2026-08-14).

   No external fonts, no external scripts, no network calls of any kind. The
   product's pitch is that nothing leaves your machine, so neither does this.
   ========================================================================== */

/* ------------------------------------------------------------------ faces */
/* Latin subset, which covers Portuguese in full. Served from assets/fonts/,
   never linked. Both are variable, so every weight the page uses comes out of
   one file per family.

   Both are SIL Open Font License 1.1, and the OFL requires the licence to
   travel with the font. Serving a woff2 from this origin is redistribution, so
   each face ships its licence beside it under the matching filename:

     assets/fonts/archivo-var-latin-v25.OFL.txt
     assets/fonts/jetbrains-mono-var-latin-v24.OFL.txt

   They are verbatim upstream text and are never edited, including the two
   punctuation rules this project applies to everything it writes itself. If a
   face is ever replaced, its licence gets the new filename with it. */

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("assets/fonts/archivo-var-latin-v25.woff2") format("woff2");
}

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url("assets/fonts/jetbrains-mono-var-latin-v24.woff2") format("woff2");
}

/* ----------------------------------------------------------------- tokens */

:root {
  /* ---- ink, the default ---- */
  --bg:       #17171A;  /* the ground, and the sheet painted over it */
  --grid:     #202024;  /* the drafting grid, seen outside the sheet only */
  --panel:    #1E1E22;  /* a card, a sheet, a panel */
  --panel-2:  #232328;  /* a bar across the top of one */
  --listing:  #131317;  /* CLI output pasted onto a panel */
  --line:     #26262B;  /* hairline */
  --line-2:   #35353D;  /* a stated border, and the ghosted ordinals */

  --ink:      #EAE8EE;  /* display, headings, the loudest mark */
  --body:     #CDCCD2;  /* running copy */
  --muted:    #B0AFB6;  /* secondary copy */
  --muted-2:  #908F96;  /* tertiary, and every mono utility label */
  --on-ink:   #17171A;  /* type on a surface filled with --ink */

  --screen:   #11111B;  /* the terminal screen */
  --screen-t: #EAE8EE;
  --screen-m: #908F96;

  /* ---- the only hue in this file ---- */
  --over:     #FF4704;  /* strokes and fills */
  --over-t:   #FF6A32;  /* the same hue at a value that carries text here */

  /* ---- what the CLI prints, following the medium and not the palette ---- */
  --t-id:     #7FB6CE;  /* a terraform address */
  --t-str:    #9BBE95;  /* a quoted string */
  --t-warn:   #E5B567;  /* WARNING, and only WARNING */

  /* ---- type ---- */
  --sans: "Archivo", ui-sans-serif, system-ui, -apple-system,
          "Segoe UI Variable Text", "Segoe UI", Helvetica, Arial, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, "SF Mono",
          Menlo, "DejaVu Sans Mono", monospace;

  /* ---- scale, 4px base ---- */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px;
  --s6: 32px; --s7: 48px; --s8: 64px; --s9: 96px; --s10: 128px;

  --max: 1160px;
  --gutter: clamp(18px, 3.2vw, 34px);
  --grid-size: 72px;
  --rail: 200px;
  --rail-gap: clamp(20px, 3vw, 48px);

  color-scheme: dark;
}

[data-mode="paper"] {
  --bg:       #EFECE5;
  --grid:     #E4E0D6;
  --panel:    #F8F6F1;
  --panel-2:  #EAE7DF;
  --listing:  #F3F0E9;
  --line:     #E2DED4;
  --line-2:   #CBC5B6;

  --ink:      #15140F;
  --body:     #38352C;
  --muted:    #55534A;
  --muted-2:  #6E6A5A;
  --on-ink:   #EFECE5;

  --screen:   #15140F;
  --screen-t: #EFECE5;
  --screen-m: #86826F;

  --over:     #FF4704;
  --over-t:   #C2360A;

  --t-id:     #2E5C7A;
  --t-str:    #4A6A46;
  --t-warn:   #8A5A00;

  color-scheme: light;
}

/* ---------------------------------------------------------------- reset */

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: 84px;
  -webkit-text-size-adjust: 100%;
  background: var(--bg);
}

/* The drafting paper. Two hairline gradients at the 72px module, painted on
   the ground itself rather than as an overlay, so there is no blend mode and
   no stacking context to reason about. The sheet covers it. */
body {
  margin: 0;
  background-color: var(--bg);
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: var(--grid-size) var(--grid-size);
  color: var(--body);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  /* Belt and braces: no face anywhere may fuse "--" into a single long dash. */
  font-variant-ligatures: none;
}

/* The sheet. Solid, so the grid only ever shows in the margin beside it, and
   two hairlines so the edge of the sheet is stated rather than implied. */
.shell {
  max-width: var(--max);
  margin: 0 auto;
  background: var(--bg);
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
}

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

h1, h2, h3 { margin: 0; color: var(--ink); }
p { margin: 0; }
ul, ol, dl { margin: 0; padding: 0; list-style: none; }

a {
  color: var(--ink);
  text-decoration-color: var(--line-2);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
a:hover { text-decoration-color: currentColor; }

code, pre, .mono, .silk, svg text {
  font-family: var(--mono);
  /* Ligatures off everywhere. JetBrains Mono fuses "--" into a single long
     dash, which would put a glyph on the page the copy deck forbids. */
  font-variant-ligatures: none;
  font-feature-settings: "liga" 0, "clig" 0, "calt" 0;
}
code, pre, .mono { font-size: 0.9em; }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.visually-hidden {
  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(--s4); top: -60px; z-index: 100;
  background: var(--ink); color: var(--on-ink);
  font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.07em;
  text-transform: uppercase; padding: 10px 18px; text-decoration: none;
  transition: top 120ms ease;
}
.skip:focus { top: var(--s4); color: var(--on-ink); }

.wrap { width: 100%; padding: 0 var(--gutter); }

/* --------------------------------------------------------- utility type */
/* Mono, uppercase, tracked open. On a drawing this is the legend, and it is
   also, everywhere on this page, the actual path or command the section is
   about, so the label carries information rather than decorating the column. */

.silk {
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.5;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.3em;
  color: var(--muted-2);
  margin: 0;
}

/* The hero eyebrow takes a short rule in front of it, which is where the
   sheet starts. One instance on the page. */
.silk-lead { display: flex; align-items: center; gap: var(--s3); }
.silk-lead::before {
  content: ""; flex: none; width: 34px; height: 1px; background: var(--line-2);
}

/* ------------------------------------------------------------------ topbar */

.topbar {
  position: sticky; top: 0; z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.topbar-in {
  padding: 0 var(--gutter);
  height: 64px; display: flex; align-items: center; gap: var(--s5);
}

.brand {
  display: inline-flex; align-items: center; gap: 9px;
  color: var(--ink); text-decoration: none; flex: none;
}
.mark { width: 20px; height: 20px; fill: none; stroke: currentColor;
        stroke-width: 1.6; stroke-linecap: square; }
.brand-word {
  font-family: var(--sans); font-size: 19px; font-weight: 900;
  letter-spacing: -0.04em;
}

.nav { display: flex; gap: var(--s5); margin-left: auto; flex-wrap: wrap; }
.nav a {
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.14em; color: var(--muted-2); text-decoration: none;
  padding: 4px 0; border-bottom: 1px solid transparent;
}
.nav a:hover { color: var(--ink); border-bottom-color: var(--ink); }

.topbar-end { display: flex; align-items: center; gap: var(--s2); margin-left: auto; flex: none; }

/* Two switches, built the same way, because they do the same kind of job: pick
   one of two states and show which one is on. Filled means on. */
.switch { display: inline-flex; border: 1px solid var(--line-2); }
.switch button {
  appearance: none; background: transparent; border: 0; cursor: pointer;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--muted-2); padding: 7px 10px;
  transition: color 140ms ease, background 140ms ease;
}
.switch button:hover { color: var(--ink); }
.switch button[aria-pressed="true"] { background: var(--ink); color: var(--on-ink); }

.ghost-btn, .solid-btn {
  font-family: var(--mono); font-size: 11.5px; text-transform: uppercase;
  letter-spacing: 0.07em; text-decoration: none; padding: 7px 15px;
  white-space: nowrap; border: 1px solid var(--line-2);
  transition: border-color 140ms ease, background 140ms ease, color 140ms ease;
}
.ghost-btn { color: var(--ink); background: transparent; }
.ghost-btn:hover { border-color: var(--ink); }
.solid-btn { background: var(--ink); color: var(--on-ink); border-color: var(--ink); }
.solid-btn:hover { background: transparent; color: var(--ink); }

/* -------------------------------------------------------------------- hero */

.hero { padding: clamp(48px, 6.4vw, 92px) 0 0; }

.hero-grid {
  display: grid; gap: clamp(32px, 4vw, 48px);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
.hero-copy .silk { margin-bottom: var(--s5); }
/* The install line has to fit on one line: a command the reader has to scroll
   to finish reading is not a one liner. This is the width that holds it. */
.hero-install { max-width: 680px; }

/* Archivo at 900, 0.88 leading, tracking pulled hard. The block itself is the
   shape: the air goes outside the headline rather than inside it, which is the
   exact inverse of the weight 300 treatment this replaces. */
.display {
  font-size: clamp(3rem, 1.1rem + 7.6vw, 7.4rem);
  line-height: 0.88;
  letter-spacing: -0.052em;
  font-weight: 900;
  max-width: 13ch;
  color: var(--ink);
  text-wrap: balance;
}
.lead {
  margin-top: var(--s5);
  font-size: clamp(1rem, 0.94rem + 0.4vw, 1.15rem);
  line-height: 1.7;
  color: var(--body);
  max-width: 52ch;
}
.micro { font-family: var(--mono); font-size: 11.5px; line-height: 1.6;
         letter-spacing: 0.04em; color: var(--muted-2); }
.hero-install .micro { margin-top: var(--s2); max-width: 72ch; }

/* ------------------------------------------------------------ the action */

.cta {
  display: inline-block; margin-top: var(--s6);
  text-decoration: none; text-align: center;
  background: var(--ink); color: var(--on-ink); border: 1px solid var(--ink);
  font-family: var(--mono); font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.1em; padding: 15px var(--s6);
  transition: background 140ms ease, color 140ms ease;
}
.cta:hover { background: transparent; color: var(--ink); }
.cta-lg { font-size: 13px; padding: 17px var(--s7); margin-top: var(--s6); }

/* The spec block. Every datasheet carries one, and every line in this one is a
   fact somebody can check in the repository before they install anything. */
.spec { margin-bottom: var(--s5); border-top: 1px solid var(--line-2); }
.spec > div {
  display: grid; grid-template-columns: 132px minmax(0, 1fr);
  gap: var(--s4); align-items: baseline;
  padding: 12px 0; border-bottom: 1px solid var(--line);
}
.spec dt {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--muted-2);
}
.spec dd { margin: 0; font-size: 15px; color: var(--body); }
.spec dd code { font-size: 12px; color: var(--muted-2); white-space: nowrap; }

/* ----------------------------------------------------------- install line */
/* One bordered row: the command on the left, the control that takes it on the
   right, a hairline between them. No title bar, no lamp, no footer. Those were
   three pieces of furniture around one line of text, and on a page that is
   already a dark instrument they were furniture imitating the room it sits in.

   This is the only way a runnable command is presented on the page. A command
   that is an illustration of a step is a different job and takes the screen
   treatment instead, in .step-art. */

.install-line {
  display: inline-flex; align-items: stretch; max-width: 100%;
  border: 1px solid var(--line-2);
  background: var(--panel);
}
.install-line .cmd { padding: 13px var(--s4); overflow-x: auto; min-width: 0; }
.install-line-lg { border-color: var(--ink); }
.install-line-lg .cmd { padding: 17px var(--s5); }

.cmd {
  margin: 0; font-family: var(--mono); font-size: 13.5px; line-height: 1.6;
  color: var(--ink); white-space: pre;
  display: flex; align-items: baseline; flex-wrap: nowrap;
}
/* Both children keep their intrinsic width so the row scrolls inside itself
   instead of clipping the command when the viewport is narrower than the line. */
.cmd .prompt, .cmd code { flex: 0 0 auto; }
.cmd code { font-size: inherit; color: inherit; }
.cmd-hero { font-size: clamp(13px, 0.6rem + 0.5vw, 16px); }

/* The prompt glyph stays neutral. herdr sets theirs in the accent, and the
   accent here is spoken for. */
.prompt { color: var(--muted-2); user-select: none; margin-right: 8px; }

.caret {
  display: inline-block; width: 8px; height: 1.05em; margin-left: 6px;
  background: var(--screen-t); vertical-align: -0.18em;
}

/* Square, borderless, divided from the command by one hairline. It is a cell
   of the row rather than a button sitting inside it. */
.copy-btn {
  appearance: none; cursor: pointer; font-family: var(--mono); font-size: 10.5px;
  letter-spacing: 0.16em; text-transform: uppercase;
  background: transparent; color: var(--muted-2);
  border: 0; border-left: 1px solid var(--line-2);
  padding: 0 var(--s4); flex: none;
  transition: background 140ms ease, color 140ms ease;
}
.copy-btn:hover { color: var(--ink); background: var(--panel-2); }
.copy-btn .copy-done { display: none; }
.copy-btn.is-copied { background: var(--ink); color: var(--on-ink); }
.copy-btn.is-copied .copy-idle { display: none; }
.copy-btn.is-copied .copy-done { display: inline; }

/* The line of facts under the command, and the way to the verifiable path.
   Platform and licence names are proper nouns and are set in the markup. */
.hero-meta {
  margin-top: var(--s4);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s5);
  font-family: var(--mono); font-size: 11.5px; line-height: 1.6;
  letter-spacing: 0.06em; color: var(--muted-2);
}
.hero-more {
  color: var(--muted); text-decoration: none;
  border-bottom: 1px solid var(--line-2);
}
.hero-more::after { content: " \2192"; }
.hero-more:hover { color: var(--ink); border-bottom-color: var(--ink); }

/* Used by the catalog entry bar, which is the last small mono label that sits
   at the right hand end of a panel header. */
.term-meta {
  font-family: var(--mono); font-size: 10.5px; color: var(--muted-2);
  letter-spacing: 0.16em; text-transform: uppercase; flex: none;
}

/* --------------------------------------------------------------- the sheet */
/* The hero drawing. Not a lit board: a fabrication drawing of one. Line weight
   does the work glow used to do, because the heaviest line is the loudest one
   and that is how a drawing has always worked. */

.sheet {
  margin: clamp(40px, 5.4vw, 72px) 0 0;
  border: 1px solid var(--line-2);
  background: var(--panel);
}
.sheet-head {
  display: flex; align-items: center; gap: var(--s3);
  padding: 10px var(--s4);
  border-bottom: 1px solid var(--line);
  background: var(--panel-2);
}
.sheet-no {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em;
  color: var(--on-ink); background: var(--ink);
  padding: 4px 9px; flex: none;
}
.sheet-cmd {
  font-family: var(--mono); font-size: 11.5px; color: var(--muted-2);
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sheet-tag {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em;
  color: var(--ink); border: 1px solid var(--line-2);
  padding: 3px 9px; flex: none;
}

/* The strip that says the drawing can be handled, and the way back once it
   has been. The reset stays hidden until something has actually moved, so the
   resting sheet carries no control the reader has no use for yet. */
.sheet-tools {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: 12px var(--s4) 0;
}
.sheet-reset {
  appearance: none; cursor: pointer; font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.16em; text-transform: uppercase;
  background: transparent; color: var(--ink);
  border: 1px solid var(--line-2); padding: 5px 12px;
  transition: border-color 140ms ease;
}
.sheet-reset:hover { border-color: var(--ink); }
.sheet-reset[hidden] { display: none; }

.sheet-face { padding: clamp(8px, 2vw, 20px); }
.sheet-face:focus-visible { outline-offset: -3px; }
.sheet-svg { width: 100%; height: auto; }
.sheet-hint { display: none; }

/* Everything inside the drawing takes its colour from the mode, so the sheet
   is repainted rather than inverted when the reader switches. */
.sheet-ground { fill: var(--panel); }
.mesh-line { fill: none; stroke: var(--line); stroke-width: 1; }
.hatch-line { stroke: var(--over); stroke-width: 1.3; opacity: 0.34; }

/* Two nodes, hairline, and the numbers are the content. */
.node rect { fill: var(--panel); stroke: var(--ink); stroke-width: 1.25; }

/* The nodes are pickable. touch-action is what stops a drag on a phone from
   scrolling the sheet sideways underneath the finger, and it has to sit on the
   element that receives the pointer rather than on the figure. */
.node {
  cursor: grab;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
.node:hover rect { stroke-width: 2; }
.node.is-held { cursor: grabbing; }
.node.is-held rect { stroke-width: 2; }
/* An outline on an SVG group is drawn around its bounding box in some engines
   and skipped in others, so the focus ring is painted on the rect itself. */
.node:focus { outline: none; }
.node:focus-visible { outline: none; }
.node:focus-visible rect { stroke-width: 3; }
.n-role { font-family: var(--mono); font-size: 11px; letter-spacing: 0.24em; fill: var(--muted-2); }
.n-name { font-family: var(--mono); font-size: 15px; fill: var(--muted); }
/* The one place on the sheet where type is allowed to be loud. Set in the
   display face at 900, the same way the stat strip and the headline are. */
.n-num  { font-family: var(--sans); font-weight: 900; font-size: 46px;
          fill: var(--ink); letter-spacing: -0.045em; }
.n-unit { font-family: var(--mono); font-weight: 400; font-size: 13px;
          fill: var(--muted-2); letter-spacing: 0.2em; }

/* Traces. Width is proportional to the load carried: 11 units for 800, and the
   two branches are cut from it in the same proportion, 6.2 and 4.8. */
.tr-load { fill: none; stroke: var(--body); stroke-width: 11; stroke-linecap: butt;
           stroke-dasharray: 16 9; }
.tr-ok   { fill: none; stroke: var(--body); stroke-width: 6.2; stroke-linecap: butt; }
.tr-over { fill: none; stroke: var(--over); stroke-width: 4.8; stroke-linecap: butt;
           stroke-linejoin: miter; }
.junction { fill: var(--ink); }

.overflow .of-frame { fill: none; stroke: var(--over); stroke-width: 1.5; }
.of-head { font-family: var(--mono); font-size: 11px; letter-spacing: 0.24em; fill: var(--over-t); }
.of-num  { font-family: var(--sans); font-weight: 900; font-size: 21px;
           fill: var(--over-t); letter-spacing: -0.03em; }

.divider { fill: none; stroke: var(--line-2); stroke-width: 1; stroke-dasharray: 2 6; }

.s-mark { font-family: var(--mono); font-size: 13px; fill: var(--over-t); }
.s-end  { font-family: var(--mono); font-size: 11px; fill: var(--muted-2); letter-spacing: 0.14em; }
.s-fill { fill: var(--body); }
.s-over { fill: none; stroke: var(--over); stroke-width: 1.2; }
.s-cut  { fill: none; stroke: var(--over); stroke-width: 2; }

/* sheet caption */
.sheet-cap {
  border-top: 1px solid var(--line);
  padding: clamp(22px, 3vw, 34px) var(--s5);
  display: grid; gap: var(--s5);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
.sheet-cap .cap-lead {
  grid-column: 1 / -1;
  font-size: clamp(1.4rem, 1rem + 1.6vw, 2.1rem);
  line-height: 1; letter-spacing: -0.045em; font-weight: 900;
  color: var(--ink); max-width: 22ch;
}
.sheet-cap p { color: var(--muted); font-size: 15px; }
.cap-key { display: grid; gap: 11px; align-content: start; }
.cap-key li { display: flex; align-items: center; gap: 12px; font-family: var(--mono);
              font-size: 11.5px; color: var(--muted); letter-spacing: 0.02em; }
/* The legend carries the drawing's own proportions, so the reader can measure
   a trace against it by eye. */
.k { display: block; height: 0; flex: none; width: 44px; }
.k-800 { border-top: 9px solid var(--body); }
.k-450 { border-top: 5px solid var(--body); }
.k-350 { border-top: 4px solid var(--over); }

/* ---------------------------------------------------------------- sections */

.section { padding: clamp(56px, 7vw, 96px) 0 clamp(60px, 7.4vw, 104px);
           border-top: 1px solid var(--line); }

.rail-grid { display: grid; gap: var(--rail-gap); grid-template-columns: var(--rail) minmax(0, 1fr); }
/* The rail label is a real path, flag or command, so it is tracked tighter
   than the eyebrow: at +0.3em "terraform show -json" runs past the gutter and
   the browser breaks it after the hyphen, which turns a flag into two words. */
.rail { padding-top: 14px; position: sticky; top: 92px; align-self: start;
        letter-spacing: 0.18em; }
.rail-body > * + * { margin-top: var(--s5); }

.rail-body h2 {
  font-size: clamp(2.1rem, 1.1rem + 3.9vw, 4.375rem);
  font-weight: 900; line-height: 0.93; letter-spacing: -0.05em;
  max-width: 19ch; text-wrap: balance;
}
.big-body { font-size: 16px; line-height: 1.7; color: var(--muted); max-width: 68ch; }
.big-body code { color: var(--body); }

.sub-h {
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.3em; color: var(--muted-2); font-weight: 400;
  padding-top: var(--s3);
}

.callout {
  border-left: 2px solid var(--line-2);
  padding: 2px 0 2px var(--s5);
  color: var(--body); font-size: 15.5px; line-height: 1.65; max-width: 64ch;
}
.callout-strong { border-left-color: var(--ink); color: var(--ink); font-size: 16.5px; }

/* ------------------------------------------------------------ problem */

/* Three scenes, set as marginalia rather than as cards: a hairline on the left
   and nothing else. They are anecdotes, and boxing an anecdote gives it the
   authority of a measurement, which is exactly what these do not have. */
.scenes { display: grid; gap: var(--s5); }
@media (min-width: 900px) { .scenes { grid-template-columns: repeat(3, 1fr); } }
.scenes li { border-left: 1px solid var(--line-2); padding-left: var(--s4); display: grid; gap: var(--s2); }
.scenes p { color: var(--muted); font-size: 14.5px; line-height: 1.6; }
.scene-tag { color: var(--ink); }

.solution { border-top: 1px solid var(--line-2); padding-top: var(--s6); display: grid; gap: var(--s4); }
.solution .statement { max-width: 20ch; }

.stages { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
.stages li { background: var(--panel); padding: var(--s5); display: grid; gap: var(--s2); }
.stage-cmd { font-family: var(--mono); font-size: 12.5px; color: var(--ink); letter-spacing: 0.02em; }
.stages p { color: var(--muted); font-size: 14.5px; line-height: 1.6; }

@media (min-width: 780px) {
  .stages { grid-template-columns: repeat(3, 1fr); }
}

/* ------------------------------------------------------ differentiators */
/* Four panels, two by two. The icon is drawn in the same hairline language as
   the hero drawing, one weight, square caps, no fill, so it reads as a detail
   lifted off the sheet rather than as an icon set bought elsewhere. */

.diffs { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
@media (min-width: 820px) { .diffs { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.diff {
  background: var(--panel); padding: var(--s5);
  display: grid; gap: var(--s3); align-content: start;
}
.diff-ico {
  width: 26px; height: 26px; fill: none; stroke: var(--ink);
  stroke-width: 1.35; stroke-linecap: square; stroke-linejoin: miter;
  margin-bottom: var(--s1);
}
.diff-h { font-size: 1.3125rem; line-height: 1.35; font-weight: 800; letter-spacing: -0.03em; }
.diff p { color: var(--muted); font-size: 14.5px; line-height: 1.6; }

/* ----------------------------------------------------------- three steps */
/* The one sequence on the page, so the one place a number is allowed to label
   anything. Full width rows, the ordinal ghosted in the gutter, the command
   the step is about on the right, hairline between them. */

.steps { display: grid; border-top: 1px solid var(--line); }
.step {
  border-bottom: 1px solid var(--line);
  padding: clamp(24px, 3vw, 34px) 0;
  display: grid; gap: var(--s4);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
.step-no {
  font-family: var(--sans); font-weight: 900;
  font-size: clamp(2rem, 1.4rem + 2vw, 3rem);
  line-height: 1; letter-spacing: -0.04em; color: var(--line-2);
}
.step-h { font-size: 1.3125rem; font-weight: 800; letter-spacing: -0.03em; line-height: 1.35; }
.step-body { display: grid; gap: var(--s2); }
.step-body p { color: var(--muted); font-size: 14.5px; line-height: 1.6; }
.step-art {
  border: 1px solid var(--line); background: var(--screen);
  padding: var(--s4); overflow-x: auto;
}
/* 11.5px, not the 12.5px the other listings take. The install one liner is 48
   characters and it has to sit on one line inside this panel: a command the
   reader has to scroll to finish reading is not a one liner. */
.step-art code {
  font-family: var(--mono); font-size: 11.5px; line-height: 1.9;
  color: var(--screen-t); white-space: pre;
}
/* The screen keeps its own prompt value: this panel is an illustration of a
   step rather than a control, so it is the one place a dark screen survives. */
.step-art .prompt { color: var(--screen-m); margin-right: 8px; }

@media (min-width: 820px) {
  .step {
    grid-template-columns: 108px minmax(0, 1fr) minmax(0, 400px);
    gap: var(--s5);
    align-items: start;
  }
}

/* ------------------------------------------------------------ findings */

.finding { border: 1px solid var(--line-2); background: var(--panel); }
.finding-head {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--line);
  background: var(--panel-2);
}
.sev {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em;
  padding: 4px 10px; flex: none;
}
.rid {
  font-family: var(--mono); font-size: 11px; color: var(--ink);
  border: 1px solid var(--line-2); padding: 2px 9px;
  flex: none; letter-spacing: 0.08em;
}
.fhead-txt { font-size: 14.5px; color: var(--body); }

/* A printed listing, not a screen: the CLI's own output pasted onto the sheet. */
.out {
  margin: 0; padding: var(--s5) var(--s4); overflow-x: auto;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.72;
  color: var(--body); background: var(--listing); white-space: pre;
  tab-size: 2;
}
.out-tight { padding: var(--s4); }

/* -------------------------------------------------- what the CLI prints

   These are not a syntax theme. They are the colours the CLI itself prints,
   reproduced here, which is why the token rules in main.js and the ones in
   internal/report/color.go are the same alternation in the same order.

   Severity is information and earns a hue. The numbers are the product and
   earn weight rather than hue. Everything else is ink, dim ink, or the one
   identifier slate.                                                        */

.t-id  { color: var(--t-id); }
.t-num { color: var(--ink); font-weight: 700; }
.t-dim { color: var(--muted-2); }

.j-k { color: var(--t-id); }
.j-s { color: var(--t-str); }
.j-c { color: var(--muted-2); font-style: italic; }

/* CRITICAL is red in the terminal, so here it takes the sheet's one hue, and
   it is the only badge allowed a background that is not ink. */
.sev-crit { background: var(--over); color: #17110D; font-weight: 700; }
.sev-warn { background: var(--t-warn); color: #17110D; font-weight: 700; }

/* Shell: the program carries weight, a flag takes the identifier slate, a URL
   goes dim. Three roles, because a command line has three kinds of word.
   These are tuned for a panel rather than for a screen, because the install
   line is the only place they appear and it is a panel. */
.t-prog { color: var(--ink); font-weight: 700; }
.t-flag { color: var(--t-id); }

/* -------------------------------------------------------------- flags */

.flags { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
.flags > div { background: var(--panel); padding: var(--s4) var(--s5);
               display: grid; gap: 4px; }
.flags dt code { font-family: var(--mono); font-size: 12.5px; color: var(--ink); }
.flags dd { margin: 0; color: var(--muted); font-size: 14.5px; line-height: 1.6; }

@media (min-width: 700px) {
  .flags > div { grid-template-columns: 210px minmax(0, 1fr); gap: var(--s5); align-items: baseline; }
}

/* -------------------------------------------------------------- rules */

.silkscreen { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
@media (min-width: 960px) {
  /* The legend is the widest thing on the page, so it reclaims the rail
     column. The rules rail stops sticking, so the two never collide.

     The two notes underneath reclaim it by exactly the same amount. They are
     commentary on the table, and commentary that starts 248px to the right of
     the thing it comments on reads as a different section. One declaration
     covers both so the next person cannot move one without the other. */
  .silkscreen,
  #rules .two-notes {
    margin-left: calc((var(--rail) + var(--rail-gap)) * -1);
  }
  .silkscreen {
    grid-template-columns: repeat(3, 1fr);
    align-items: stretch;
  }
  #rules .rail { position: static; }
}

.chip-group { background: var(--panel); padding: var(--s5); }
.cg-head {
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink); font-weight: 400;
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
  padding-bottom: var(--s3); border-bottom: 1px solid var(--line);
}
.cg-count { color: var(--muted-2); font-size: 11px; letter-spacing: 0.16em; }
.rulelist { display: grid; gap: var(--s3); padding-top: var(--s4); }
.rulelist li { display: grid; grid-template-columns: 46px minmax(0, 1fr); gap: var(--s3);
               align-items: start; font-size: 13.5px; color: var(--muted); line-height: 1.55; }
.rulelist .rid { text-align: center; padding: 2px 0; font-size: 10.5px; }
.rulelist code { color: var(--body); font-size: 11.5px; word-break: break-word; }

/* Two equal columns, and the split deliberately does not chase the seams of
   the three cloud panels above. Two notes over three clouds is not a mapping,
   and forcing one would sit an AWS caveat under the Google Cloud column. What
   has to line up is the outer edge, and it does. */
.two-notes { display: grid; gap: var(--s5); }
@media (min-width: 820px) { .two-notes { grid-template-columns: 1fr 1fr; gap: var(--s7); } }

/* ------------------------------------------------------------ privacy */

.statement {
  font-size: clamp(1.6rem, 1.1rem + 2.2vw, 2.9rem);
  line-height: 0.98; letter-spacing: -0.04em; font-weight: 900;
  color: var(--ink); max-width: 17ch;
}

.allow { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
@media (min-width: 760px) { .allow { grid-template-columns: 1fr 1fr; } }
.allow-col { padding: var(--s5); background: var(--panel); }
.allow-yes { box-shadow: inset 2px 0 0 var(--ink); }
.allow-col h3 { margin-bottom: var(--s4); font-weight: 400; }
.attrs { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s4); }
.attrs li { font-family: var(--mono); font-size: 11.5px; }
.attrs code { color: var(--body); background: var(--listing); border: 1px solid var(--line-2);
              padding: 5px 9px; display: inline-block; }
.allow-no .attrs code { color: var(--muted-2); text-decoration: line-through;
                        text-decoration-color: var(--line-2); }
.attr-etc { font-family: var(--mono); font-size: 11.5px; color: var(--muted-2); padding: 5px 0; display: inline-block; }

.facts { display: grid; gap: var(--s3); max-width: 72ch; }
.facts li { position: relative; padding-left: var(--s5); color: var(--muted); font-size: 15px; line-height: 1.65; }
.facts li::before {
  content: ""; position: absolute; left: 0; top: 0.72em;
  width: 11px; height: 1px; background: var(--ink);
}

.dep { display: grid; gap: var(--s5); align-items: center; }
@media (min-width: 820px) { .dep { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.dep-code { border: 1px solid var(--line-2); background: var(--panel); }
.dep-code .silk { padding: 10px var(--s4); border-bottom: 1px solid var(--line);
                  background: var(--panel-2); }
.dep-note { color: var(--muted); font-size: 15px; line-height: 1.65; }

/* ------------------------------------------------------------ catalog */

.entry { border: 1px solid var(--ink); background: var(--panel); }
.entry-bar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: 10px var(--s4); border-bottom: 1px solid var(--line);
  background: var(--panel-2);
}
.entry .out { font-size: 12px; }

/* ------------------------------------------------------------ pricing */
/* Three columns, and the middle one is raised by ink alone: a heavier border,
   an ink cap above it and an ink button inside it. On a sheet with one hue
   reserved for the overflow, emphasis has to be carried by weight, the same
   trade the drawing makes with its trace widths. */

.plans { display: grid; gap: var(--s3); align-items: start; }
@media (min-width: 900px) {
  .plans { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* The featured column stands proud of its neighbours and keeps its own
     vertical rhythm, so the three price lines still sit on one baseline. */
  .plan-feature { margin-top: calc(var(--s6) * -1); }
}

.plan {
  display: flex; flex-direction: column; gap: var(--s3);
  border: 1px solid var(--line-2); background: var(--panel); padding: var(--s5);
}
.plan-feature { border-color: var(--ink); }
/* The cap that marks the recommended column. Ink, not hue. */
.plan-flag {
  align-self: flex-start; color: var(--on-ink); background: var(--ink);
  padding: 4px 11px; margin-bottom: var(--s1);
}

.plan-name { color: var(--ink); }
.plan-price { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.plan-amt {
  font-size: clamp(2.4rem, 1.6rem + 2.6vw, 3.6rem); line-height: 1;
  font-weight: 900; letter-spacing: -0.045em; color: var(--ink);
}
.plan-amt-quote { font-size: clamp(1.6rem, 1.2rem + 1.2vw, 2.2rem); }
.plan-per { font-family: var(--mono); font-size: 11px; color: var(--muted-2);
            letter-spacing: 0.16em; text-transform: uppercase; }
.plan-sub { font-size: 14.5px; color: var(--body); }

.plan-feats { display: grid; gap: var(--s3); margin-top: var(--s2);
              padding-top: var(--s4); border-top: 1px solid var(--line); }
.plan-feats li { position: relative; padding-left: var(--s5);
                 font-size: 14px; line-height: 1.55; color: var(--muted); }
.plan-feats li::before {
  content: ""; position: absolute; left: 0; top: 0.66em;
  width: 11px; height: 1px; background: var(--ink);
}

/* The button sits at the bottom of every card regardless of list length, so
   the three actions line up instead of following the copy. */
.plan-cta {
  margin-top: auto; text-align: center; text-decoration: none;
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.12em; padding: 12px var(--s4);
  border: 1px solid var(--ink); background: var(--ink); color: var(--on-ink);
  transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}
.plan-cta:hover { background: transparent; color: var(--ink); }
.plan-cta-ghost { background: transparent; color: var(--ink); border-color: var(--line-2); }
.plan-cta-ghost:hover { background: transparent; color: var(--ink); border-color: var(--ink); }
/* Not a link and not a button: the plan cannot be bought yet, so nothing here
   is clickable and the label says so rather than implying a dead control. */
.plan-cta-soon {
  background: transparent; color: var(--muted-2); border: 1px dashed var(--line-2);
  cursor: default;
}
.plan-note { margin-top: var(--s2); }
.plan-lock {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--muted-2);
}

/* ------------------------------------------------------------ install */

.dl-grid { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line);
           grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.dl {
  display: grid; gap: 2px; text-decoration: none;
  background: var(--panel); padding: var(--s4) var(--s5);
  transition: background 140ms ease;
}
.dl:hover { background: var(--panel-2); }
.dl-os { font-size: 15px; font-weight: 800; letter-spacing: -0.02em; color: var(--ink); }
.dl-arch { font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em;
           text-transform: uppercase; color: var(--muted-2); }
.dl-file { font-family: var(--mono); font-size: 10.5px; color: var(--muted-2);
           margin-top: 7px; word-break: break-all; }

/* --------------------------------------------------------- closing call */
/* Left aligned, at the hero's own scale. The ending is signalled by the page
   repeating its opening voice one last time, which is stronger than centring
   a block on a sheet where every other line hangs off the same left edge. */

.finalcta-in { display: grid; gap: var(--s5); justify-items: start; }
.finalcta-h {
  font-size: clamp(2.4rem, 1.1rem + 5vw, 5.2rem);
  line-height: 0.9; letter-spacing: -0.05em; font-weight: 900;
  max-width: 15ch; text-wrap: balance;
}
.finalcta-lead {
  font-size: clamp(1rem, 0.94rem + 0.4vw, 1.15rem);
  line-height: 1.7; color: var(--muted); max-width: 48ch;
}
.finalcta .cta { margin-top: 0; }

/* ------------------------------------------------------------- footer */

.foot { border-top: 1px solid var(--line); padding: clamp(48px, 6vw, 80px) 0 var(--s5); }
.foot-in { display: grid; gap: var(--s6); align-items: start; }
@media (min-width: 860px) {
  .foot-in { grid-template-columns: 1.6fr 1fr 1fr auto; gap: var(--s5); }
}
.foot-brand { display: grid; gap: var(--s3); align-content: start; }
.foot-brand .brand { width: fit-content; }
.foot-line { color: var(--muted); font-size: 14.5px; line-height: 1.6; max-width: 30ch; }

.foot-nav { display: grid; gap: var(--s2); align-content: start; }
.foot-navh { margin-bottom: var(--s1); }
.foot-nav a {
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.14em; text-decoration: none; color: var(--muted-2);
  width: fit-content; border-bottom: 1px solid transparent;
}
.foot-nav a:hover { color: var(--ink); border-bottom-color: var(--ink); }

/* One icon, because one account exists. An empty row of social marks is a
   claim about reach this project has not earned yet. */
.foot-social { display: flex; gap: var(--s3); align-items: center; }
.social {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px;
  border: 1px solid var(--line-2); background: transparent; color: var(--ink);
  transition: border-color 140ms ease;
}
.social:hover { border-color: var(--ink); }
.social svg { width: 16px; height: 16px; fill: currentColor; }

.foot-base {
  margin-top: clamp(32px, 5vw, 56px); padding-top: var(--s5);
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: var(--s3) var(--s5);
  align-items: baseline; justify-content: space-between;
}
.foot-copy { font-family: var(--mono); font-size: 10.5px; color: var(--muted-2);
             letter-spacing: 0.16em; text-transform: uppercase; }
.foot-base-links { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s3);
                   font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.08em;
                   color: var(--muted-2); }
.foot-base-links a { color: var(--muted-2); text-decoration: none;
                     border-bottom: 1px solid var(--line-2); }
.foot-base-links a:hover { color: var(--ink); border-bottom-color: var(--ink); }
.foot-sep { width: 1px; height: 10px; background: var(--line-2); align-self: center; }

/* ------------------------------------------------------------ responsive */

@media (max-width: 1160px) {
  /* Below the column width there is no margin for the grid to live in, so the
     sheet runs edge to edge and the two side rules go with it. */
  .shell { border-left: 0; border-right: 0; }
}

@media (max-width: 1120px) {
  .nav { display: none; }
  .display { max-width: 15ch; }
}

/* 959.98 and not 960. A max-width and a min-width written at the same integer
   both match at exactly that width, and the pair here is not harmless: the rail
   would collapse to one column while the rules table and its notes still carry
   the negative margin that reclaims the rail, so at exactly 960px both blocks
   hung 229px off the left edge of the sheet and the page scrolled sideways.
   Caught by measuring, not by looking: 960px is a width nobody opens by hand. */
@media (max-width: 959.98px) {
  .rail-grid { grid-template-columns: minmax(0, 1fr); }
  .rail { position: static; padding-top: 0; }
  .sheet-cap { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 720px) {
  /* The drawing keeps its proportions and scrolls sideways rather than
     shrinking the figures below the point where they can be read. */
  .sheet-face { padding: 0; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .sheet-face .sheet-svg { min-width: 800px; }
  .sheet-hint {
    display: block; padding: 12px var(--s4) 0;
    font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em;
    text-transform: uppercase; color: var(--muted-2);
  }
  .sheet-cmd { font-size: 10.5px; }
  .topbar-in { gap: var(--s2); height: 58px; }
  .ghost-btn { display: none; }
  .cmd { font-size: 12.5px; }
  .out { font-size: 11.5px; padding: var(--s4) var(--s3); }
  .stages li, .chip-group, .allow-col, .flags > div,
  .diff, .plan { padding: var(--s4); }
  .sheet-cap { padding: var(--s5) var(--s4); }
  .cta { width: 100%; padding: 14px var(--s5); }
  .cta-lg { font-size: 12px; padding: 15px var(--s5); }
}

@media (max-width: 480px) {
  /* The sticky bar cannot hold brand, two switches and a button at this width:
     it overflowed by five pixels and scrolled the whole page sideways. The
     button is what goes, because it is the only control up there that is a
     duplicate. The install line is the action and it sits one screen down,
     which is the same reason the hero carries no button either. */
  .solid-btn { display: none; }

  /* Keep the install one liner on a single readable line at phone widths. */
  .cmd, .cmd-hero { font-size: 11.5px; }
  .install-line .cmd, .install-line-lg .cmd { padding: 12px var(--s3); }
  .copy-btn { padding: 0 var(--s3); }
  .prompt { margin-right: 6px; }
  .sheet-head { padding: 10px var(--s3); gap: var(--s2); }
  .sheet-cmd { display: none; }
  .spec > div { grid-template-columns: 100px minmax(0, 1fr); gap: var(--s3); }
}

@media (max-width: 400px) {
  :root { --gutter: 16px; }
  .brand-word { font-size: 17px; }
  .solid-btn { padding: 6px 11px; font-size: 10.5px; }
  .switch button { padding: 6px 8px; }
}

/* ============================================================== motion ====
   The sheet plots itself once on load: the two nodes, then the edge drawing
   itself from the service toward the database, then what fits, then what does
   not. The overflow arrives last because it is the punchline.

   One deliberate exception to "once on load". After the edge has drawn, its
   dashes keep marching toward the database. The rest of the page holds still.
   The edge is the entire product thesis, 800 connections pouring into a wall
   that takes 450, and a loop is the only way to make that something the reader
   watches rather than reads. Spending the page's one continuous motion here is
   the reason nothing else gets any, including the mode toggle.

   Everything below is additive. The resting state is the finished drawing, so
   a reader who has asked for less motion is not waiting for anything and
   nothing has to be undone at runtime.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  @keyframes plot  { to { stroke-dashoffset: 0; } }
  @keyframes ink   { from { opacity: 0; } to { opacity: 1; } }
  @keyframes rise  { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
  @keyframes blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
  /* The clip that uncovers the derived edge, left to right. */
  @keyframes wipe  { from { transform: scaleX(0); } to { transform: scaleX(1); } }
  /* One dash plus one gap, so the pattern lands exactly where it started. */
  @keyframes march { to { stroke-dashoffset: -25; } }

  .caret { animation: blink 1.15s steps(1, end) infinite; }

  .sheet-svg .node,
  .sheet-svg .junction,
  .sheet-svg .overflow,
  .sheet-svg .divider,
  .sheet-svg .s-mark,
  .sheet-svg .s-end,
  .sheet-svg .s-fill,
  .sheet-svg .s-hatch,
  .sheet-svg .s-over,
  .sheet-svg .s-cut {
    animation: ink 420ms ease-out both;
  }

  .sheet-svg .node { animation-name: rise; animation-duration: 520ms; }
  /* Keyed off the data attribute rather than nth-of-type: each .node is the
     only child of its positioning group, so nth-of-type would never match. */
  .sheet-svg [data-node="provider"] .node { animation-delay: 90ms; }

  /* transform-box keeps the origin in viewBox units, so the wipe starts at the
     consumer's right edge rather than at the rect's own bounding box. The rect
     covers the whole sheet, so when the animation finishes the clip is a no op
     and a dragged edge is never cut by it. */
  .sheet-svg .wipe-rect {
    transform-box: view-box;
    transform-origin: 320px 144px;
    animation: wipe 760ms cubic-bezier(0.32, 0, 0.2, 1) 320ms both;
  }

  /* Once drawn, the load keeps moving. */
  .sheet-svg .tr-load { animation: march 1.5s linear 1100ms infinite; }

  .sheet-svg .tr-ok,
  .sheet-svg .tr-over {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: plot 520ms cubic-bezier(0.32, 0, 0.2, 1) both;
  }

  .sheet-svg .junction { animation-delay: 1040ms; }
  .sheet-svg .tr-ok    { animation-delay: 1080ms; animation-duration: 360ms; }
  .sheet-svg .tr-over  { animation-delay: 1220ms; animation-duration: 620ms; }
  .sheet-svg .overflow { animation-delay: 1620ms; animation-duration: 560ms; }
  .sheet-svg .divider,
  .sheet-svg .s-end,
  .sheet-svg .s-fill   { animation-delay: 1560ms; }
  .sheet-svg .s-hatch,
  .sheet-svg .s-over,
  .sheet-svg .s-cut,
  .sheet-svg .s-mark   { animation-delay: 1740ms; }
}

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

/* main.js mirrors the same query at runtime for anything that has to be told
   twice. Nothing here depends on it: the class only silences, it never reveals. */
.motion-off .caret { animation: none; opacity: 1; }
.motion-off .sheet-svg * { animation: none !important; opacity: 1; stroke-dashoffset: 0; }
/* The edge is dashed because it is derived, not because it is animating, so
   the pattern survives the silencing. The wipe rect has no transform without
   its animation, which leaves the clip at full width and the edge fully drawn. */
.motion-off .sheet-svg .tr-load { stroke-dasharray: 16 9; }

/* ==========================================================================
   THE PLAYGROUND

   One page, /playground, and everything below is used only there.

   It is a bench with two halves: a box where a plan is pasted, and the screen
   the analyzer prints to. That screen is the one place on this site drawn as a
   screen rather than as a printed listing, because here the output is
   happening rather than being quoted, and that difference is the feature.

   The palette does not move. The editor highlights with the same listing
   tokens the CLI prints with, plus weight and dim ink for everything else, so
   no hue enters the sheet that was not already spoken for. Severity on the
   screen is the CLI's own colour, which the design system has always allowed,
   because severity is information.
   ========================================================================== */

.bench { display: grid; gap: var(--s5); }

/* A pane is a panel with a bar across the top of it, the same construction as
   .finding, so the two read as one object doing two jobs. */
.pane { border: 1px solid var(--line-2); background: var(--panel); min-width: 0; }
.pane-bar {
  display: flex; align-items: stretch; flex-wrap: wrap;
  border-bottom: 1px solid var(--line-2); background: var(--panel-2);
  min-height: 38px;
}
.pane-name {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--muted-2);
  display: flex; align-items: center; padding: 0 var(--s4); flex: none;
}
.pane-name b { color: var(--ink); font-weight: 400; }
.pane-gap { flex: 1 1 auto; min-width: var(--s4); }

/* Every control in a bar is a cell of the bar, divided by one hairline, never
   a button floating inside it. The same rule the copy control follows on the
   install line. */
.bar-btn {
  appearance: none; cursor: pointer; font-family: var(--mono); font-size: 10.5px;
  letter-spacing: 0.16em; text-transform: uppercase;
  background: transparent; color: var(--muted-2);
  border: 0; border-left: 1px solid var(--line-2);
  padding: 9px var(--s4); flex: none;
  transition: background 140ms ease, color 140ms ease;
}
.bar-btn:hover:not(:disabled) { color: var(--ink); background: var(--panel); }
.bar-btn:disabled { opacity: 0.45; cursor: default; }
.bar-btn[aria-pressed="true"] { background: var(--ink); color: var(--on-ink); }

/* -------------------------------------------------------------- the editor

   Three layers over one cell. The text box is on top and transparent, a
   highlighted copy of the same text sits under it, and the line numbers sit
   beside them. All three share the face, the size, the leading and the
   padding, which is what puts the caret on the glyph painted beneath it.
   Changing one of those four and not the other three is how this breaks. */

/* One height for the whole pane, declared on the container so all three layers
   inherit it. The line numbers need it as much as the text does: the gutter
   clips its overflow but nothing was capping its height, so a thousand line
   plan made the element itself a thousand lines tall and the page grew to
   thirty thousand pixels of empty ground below the fold. */
.editor {
  --pane-h: 46vh;
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  background: var(--listing);
}

.ed-gutter {
  height: var(--pane-h); min-height: 260px;
  overflow: hidden; padding: var(--s4) 0 var(--s4) var(--s4);
  border-right: 1px solid var(--line);
  color: var(--muted-2); text-align: right; user-select: none;
}
.ed-gutter pre { margin: 0; padding-right: var(--s3); }

.ed-stack { position: relative; min-width: 0; }

.ed-gutter pre, .ed-hl, .ed-input {
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.72;
  tab-size: 2;
  white-space: pre;
  font-variant-ligatures: none;
}

.ed-hl, .ed-input {
  margin: 0; padding: var(--s4);
  border: 0; width: 100%; height: var(--pane-h); min-height: 260px;
  overflow: auto;
}
.ed-hl {
  position: absolute; inset: 0; pointer-events: none;
  color: var(--body); background: transparent;
  overflow: hidden;
}
.ed-input {
  position: relative; display: block; resize: none;
  background: transparent; color: transparent; caret-color: var(--ink);
  outline: 0;
}
.ed-input::placeholder { color: var(--muted-2); opacity: 1; }
.ed-input:focus-visible { outline: 2px solid var(--over); outline-offset: -2px; }

/* The editor's tokens. A key takes the identifier slate the CLI gives a
   terraform address, a string takes the string green, and the rest is ink,
   bold ink or dim ink. Five roles, no new hue. */
.e-k { color: var(--t-id); }
.e-s { color: var(--t-str); }
.e-n { color: var(--ink); font-weight: 700; }
.e-b { color: var(--muted); }
.e-p { color: var(--muted-2); }

/* A note under the box, for the two states worth stating: how much was pasted,
   and the one case where the highlighting steps aside. */
.ed-note {
  display: flex; gap: var(--s4); flex-wrap: wrap;
  border-top: 1px solid var(--line);
  padding: var(--s3) var(--s4);
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em;
  color: var(--muted-2);
}
.ed-note b { color: var(--body); font-weight: 400; }
.ed-note .is-bad { color: var(--over-t); }

/* ---------------------------------------------------------------- the run */

.runbar {
  display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap;
  padding: var(--s4) 0;
}
.run-btn {
  appearance: none; cursor: pointer;
  font-family: var(--mono); font-size: 12.5px; text-transform: uppercase;
  letter-spacing: 0.1em; padding: 13px var(--s6);
  background: var(--ink); color: var(--on-ink); border: 1px solid var(--ink);
  transition: background 140ms ease, color 140ms ease;
}
.run-btn:hover:not(:disabled) { background: transparent; color: var(--ink); }
.run-btn:disabled { opacity: 0.5; cursor: default; }
.run-hint { font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em;
            color: var(--muted-2); }
.run-hint kbd {
  font-family: var(--mono); font-size: 11px; color: var(--body);
  border: 1px solid var(--line-2); padding: 2px 6px;
}

/* --------------------------------------------------------------- the screen

   The only screen on this site. Everywhere else quotes output that was already
   printed, and sets it as a listing on paper. This one is being written to
   while somebody watches. */

.screen {
  --pane-h: 46vh;
  margin: 0; padding: var(--s5) var(--s4);
  background: var(--screen); color: var(--screen-t);
  font-family: var(--mono); font-size: 12.5px; line-height: 1.72;
  white-space: pre; overflow: auto; tab-size: 2;
  height: var(--pane-h); min-height: 260px;
  font-variant-ligatures: none;
}
.screen:focus-visible { outline: 2px solid var(--over); outline-offset: -2px; }

/* What the analyzer prints, mapped from its own escapes. Eight colour ANSI is
   everything it emits, and this is the whole table.

   These three do not follow the sheet, and that is the point. The listing
   tokens elsewhere on this site are tuned for printed output on paper, so in
   paper mode they darken: --t-id goes from #7FB6CE to #2E5C7A because it is
   about to sit on a near white listing. The screen is dark in BOTH modes, so a
   token that darkened for paper put a dark blue terraform address on a near
   black terminal, which was the one unreadable thing on the page.

   So the screen keeps the ink values in both sheets. No new hue enters: they
   are the same three colours the design system already names, pinned to the
   surface they are actually painted on. */
.screen {
  --s-id: #7FB6CE;
  --s-warn: #E5B567;
  --s-crit: #FF6A32;
}

.a-b { font-weight: 700; }
.a-d { color: var(--screen-m); }
.a-red { color: var(--s-crit); font-weight: 700; }
.a-yel { color: var(--s-warn); font-weight: 700; }
.a-cya { color: var(--s-id); font-weight: 700; }
.a-id { color: var(--s-id); }

/* The page's own lines, which are not the analyzer's. Dim, and behind a
   marker, so nothing the page says can be read as something the tool said. */
.s-say { color: var(--screen-m); }
.s-ok { color: #9BBE95; }
.s-bad { color: var(--s-crit); }
.s-cmd { color: var(--screen-t); font-weight: 700; }

/* A caret exists only while something is being written. */
.s-live::after {
  content: "_";
  color: var(--over);
  animation: blink 1s steps(1) infinite;
}

/* ----------------------------------------------------------------- panels */

/* The panels below the bench: what to put in a pipeline once the report made
   the case, and what this page sent. Both start hidden and are revealed by the
   first run, because neither has anything true to say before one. */
.after { display: none; }
.after.is-on { display: block; }

.snip {
  margin: 0; padding: var(--s4); overflow-x: auto;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.72;
  color: var(--body); background: var(--listing); white-space: pre; tab-size: 2;
}

.sends { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
.sends > div { background: var(--panel); padding: var(--s4) var(--s5); }
.sends dt { font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em;
            text-transform: uppercase; color: var(--muted-2); }
.sends dd { margin: var(--s2) 0 0; color: var(--body); font-size: 14.5px; line-height: 1.6; }

/* ------------------------------------------------------------------ wide */

/* The two panes stack at every width, and that is a decision rather than a
   fallback. The report is wrapped by the CLI at 76 columns and its detail
   lines are indented under that, so it wants about 80 columns to read without
   being cut. At 12.5px that is roughly 600px of glyphs, and two of those side
   by side plus their padding do not fit inside a 1160px column: the pair only
   works by shrinking the type or by putting a horizontal scrollbar under the
   one thing on this page somebody is here to read.

   Stacked, the screen gets the whole column and the report never wraps twice.
   The cost is that the output starts below the fold, which playground.js pays
   by scrolling the screen into view when a run starts. */
@media (min-width: 860px) {
  .bench .runbar { justify-content: center; }
  .editor { --pane-h: 38vh; }
  .screen { --pane-h: 56vh; }
}

/* An empty line has to occupy a line.

   Every line on the screen is its own element, which is what lets the report
   arrive a few lines per frame. An element with no text has no height, so the
   blank lines the report uses to separate one finding from the next were
   collapsing and the output came out as one wall. The space is added in the
   stylesheet rather than in the text so that selecting the report and copying
   it still yields the bytes the CLI printed. */
.screen div:empty::after { content: " "; }

/* Two notes that hang off the bench rather than sitting inside a pane. Classes
   and not inline style, because the CSP is style-src 'self' with no
   unsafe-inline and a style attribute would simply be dropped. */
.pg-lede-note { margin-top: var(--s5); max-width: 72ch; }
.pg-bench-note { margin-top: var(--s2); max-width: 78ch; }
