/* The shell and the home page: layers, chrome, buttons, hero, sections,
 * bento, the stepped rail and the footer.
 *
 * Was the whole stylesheet at 1522 lines, against a ceiling of 750. Split on
 * 2026-08-01 into pages.css and motion.css by cutting it in two places, both
 * on a blank line at brace depth zero, so no rule and no `@media` block was
 * divided and the cascade did not move.
 *
 * A new sheet is a new `<link>` in base.html AND a new line in `VERSIONED` in
 * crates/web/src/assets.rs. Left out of that list its version never moves and
 * a returning visitor keeps the old copy for a year. */
*, *::before, *::after { box-sizing: border-box; }

body {
  position: relative;
  margin: 0;
  color: var(--text-1);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  background: var(--bg-0);
}

/* Two layers, and the split matters. This one spans the whole document, so the
   page is one long ramp of light and any section boundary lands mid ramp
   instead of between two identical plates. A fixed layer cannot do this: it
   repeats the same light under every screenful. */
body::before {
  content: ""; position: absolute; inset: 0; z-index: -2; pointer-events: none;
  background: var(--page-ramp);
}

/* This one is fixed, so scrolling still feels lit from somewhere. */
body::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(120% 60% at 50% -10%, rgba(255,255,255,.022), rgba(255,255,255,0) 55%);
}

/* Film grain. Long dark gradients band badly on real panels, and the noise is
   what makes the page read as a surface rather than as a fill. */
body > .grain {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  opacity: .055; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>");
}
.page-top, main, .site-foot { position: relative; z-index: 1; }

h1, h2, h3, h4 {
  font-family: var(--font-display); font-weight: 600; line-height: 1.02;
  margin: 0; letter-spacing: -0.028em; text-wrap: balance;
}
p { margin: 0; }
h1, h2, h3, .mono, .node h3 { overflow-wrap: anywhere; }
a { color: inherit; text-decoration: none; }
svg { display: block; }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.eyebrow {
  display: inline-flex; align-items: center; gap: var(--step-3);
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--text-3);
}
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
.wrap { width: min(1180px, 100% - var(--step-10)); margin-inline: auto; }
html { scroll-behavior: smooth; }
/* The one piece of chrome the OS draws inside our page, and left alone it is a
   light grey bar down the edge of a near black site. Declared on both, because
   the viewport's bar takes the ROOT's value while `body` is where a service
   page redefines the accent — so a list that scrolls inside one inherits that
   page's own hue instead of the site default. */
html, body { scrollbar-color: rgb(var(--accent-rgb) / .38) rgb(255 255 255 / .04); }
[id] { scroll-margin-top: 88px; }

/* the mark, and the page's own control ---------------------------------- */

/* All that is left of the header: the way home on the left, and whatever this
   page puts on the right. On the page background, no bar - no blur, no
   hairline, not sticky, and it scrolls away like anything else. */
.page-top .wrap { display: flex; align-items: center; gap: var(--step-6); padding-top: var(--step-4); }
.mark {
  display: inline-flex; align-items: center; min-height: 44px;
  font-family: var(--font-display); font-size: 17px; font-weight: 600; letter-spacing: -0.02em;
}
.mark span { color: var(--text-3); }
/* Pushed right whatever the mark's width, and still right when there is no
   action at all. */
.page-top .wrap > :not(.mark) { margin-left: auto; }

/* The page you are on, in the footer. A nav that looks the same on every page
   answers where you can go and not where you are, and the second question is
   the one somebody four pages deep is actually asking. */
.site-foot nav a[aria-current] { color: var(--text-1); }

/* icons ------------------------------------------------------------------ */

/* Icons. One sprite in base.html, referenced by <use>, so an icon costs a line
   at the call site and inherits the colour of the text it sits with. */
/* Out of flow here rather than in a style attribute on the svg: the page runs
   `style-src 'self'` with no unsafe-inline, so the attribute was blocked and
   logged a CSP violation on every single page load. */
/* In the SHELL sheet, not in pages.css, because base.html includes the sprite on
   every page: the signed out home page draws these too. It sat behind the sign
   in until 2026-08-01, which made the one rule that gives every icon its stroke
   unfindable from the page that uses it most. Every override of it - `.p-ic .ic`,
   `.stack-ic .ic` - carries more specificity, so this rule
   losing its place in the link order costs nothing. */
.icon-sprite { position: absolute; }
.ic {
  width: 20px; height: 20px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
/* An eyebrow is 11px uppercase, so its cap height is about 8px and the default
   20px icon beside it is nearly two and a half times that - the glyph reads as
   the heading and the word as its caption. `.p-ic .ic` and `.stack-ic .ic` were
   both re-sized for their own context and this one was missed. */
.eyebrow .ic { width: 14px; height: 14px; }

/* buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--step-2);
  height: 44px; padding: 0 var(--step-5);
  border: 1px solid var(--border-str); border-radius: var(--r-md);
  background: linear-gradient(180deg, rgba(255,255,255,.05), transparent), var(--surface-2);
  color: var(--text-1);
  font: inherit; font-size: 15px; font-weight: 500; cursor: pointer;
  box-shadow: var(--shadow-raised);
  transition: transform 140ms ease-out, filter 140ms ease-out;
}
.btn:hover { transform: translateY(-1px); filter: brightness(1.16); }
.btn:active { transform: translateY(0); }
.btn-primary {
  background: var(--accent); color: var(--accent-ink);
  border-color: transparent; font-weight: 600;
  box-shadow: inset 0 -1px 0 rgba(0,0,0,.22);
}
.btn-lg { height: 52px; padding: 0 28px; font-size: 16px; }
.btn-block { width: 100%; margin-top: var(--step-5); }

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

/* The glows are wider than the viewport by design. Clip on one axis only: the
   lit bands still need to bleed up and down past their own section. */
.hero, .page-head, .section { overflow-x: clip; }
/* An engine that does not know `clip` drops the declaration and falls back to
   `visible`, and the glows are deliberately wider than the viewport: the hero's
   is `left: 68%; width: 1000px` translated -50%, so its right edge lands at
   0.68W + 500 -- past the viewport at any normal width, giving the whole
   document a horizontal scrollbar and rubber-band on touch. Hiding the glows is
   the fallback, NOT `overflow-x: hidden`: `hidden` would make the section a
   scroll container and break the `position: sticky` children inside it. */
@supports not (overflow-x: clip) {
  .hero::after, .page-head::after, main > .section + .section::after { display: none; }
}
.hero { position: relative; padding: var(--step-20) 0 var(--step-16); }
.hero::after {
  content: ""; position: absolute; pointer-events: none;
  left: 68%; top: -240px; width: 1000px; height: 720px; transform: translateX(-50%);
  background: radial-gradient(45% 45% at 50% 50%, rgb(var(--accent-rgb) / .15), transparent 72%);
  filter: blur(8px);
}
/* Left the type, right the object. A centred stack of headline, sub and two
   buttons is the single most generated shape on the web. */
.hero .wrap {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.04fr);
  gap: var(--step-10); align-items: center;
}
.hero h1 { font-weight: 700; font-size: clamp(36px, 4vw, 54px); max-width: 16ch; }
.hero h1 .h-a { color: var(--text-1); }
.hero h1 .h-b { color: var(--accent); }
.hero-sub { color: var(--text-2); font-size: 18px; margin-top: var(--step-6); max-width: 42ch; }
.hero-actions { display: flex; gap: var(--step-3); margin-top: var(--step-8); flex-wrap: wrap; }

/* The rail. The icon in the browser tab, used twice and inverted: the balance
   is that tile as drawn, and a service is the same tile turned inside out. It
   is a grid rather than a drawing because the constellation it replaced placed
   every label by hand, so a label sat on its own arm and a withheld arm left a
   hole. Cells cannot overlap and a missing row closes itself. */
.rail {
  /* One track width for every tile, so the head and the rows centre on the same
     axis and the hairline is that axis halved rather than a number that happened
     to look right. The first cut sized each tile to itself: a 56px head and a
     38px row in an `auto` track left the small ones against the track's left
     edge, nine pixels off the big one, and the line aligned to neither. */
  --rail-w: 64px;
  display: grid; gap: var(--step-6); align-content: center; justify-content: center;
}
/* `padding: 0` is load bearing, not tidying. A `ul` carries 40px of
   padding-inline-start by default, which pushed every row 40px right of the
   balance above it and took the hairline, positioned inside this box, along
   with it. The tiles looked deliberately indented and the line ran down their
   left edge instead of their centre. */
.rail-list { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--step-5); }

/* Both rows are the same two column track, so every tile, every name and every
   note shares one edge down the whole column. That alignment is the entire
   point and it is free here: the browser measures it, nothing here guesses. */
.rail-top, .rail-row {
  display: grid; grid-template-columns: var(--rail-w) minmax(0, 26ch);
  column-gap: var(--step-4); align-items: center;
}
.rail-name { font-family: var(--font-mono); font-size: 13px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--text-1); align-self: end; }
.rail-note { grid-column: 2; font-size: 15px; color: var(--text-3); align-self: start; }
.rail-top .rail-name { font-size: 14px; color: var(--accent-lift); }
.rail-top .rail-note { font-size: 16px; color: var(--text-2); }

/* The tile spans both text rows, so its centre lands between the name and its
   note rather than on either one. Centred in the track, which is what puts every
   tile on the one axis the hairline runs down. */
.rail-tile {
  grid-row: 1 / 3; justify-self: center; display: grid; place-items: center;
  width: 42px; height: 42px; border-radius: var(--r-md);
  border: 1px solid rgb(var(--accent-rgb) / .4);
}
.rail-tile-on {
  width: var(--rail-w); height: var(--rail-w); border: 0;
  background: var(--accent); border-radius: var(--r-lg);
}
.rail-star { width: 22px; height: 22px; fill: var(--accent); }
/* Knocked out in the page's own colour, and filling the tile the way it does in
   the browser tab: the icon is a star nearly the height of its own tile, and a
   small mark floating in the middle of one is a different drawing. */
.rail-tile-on .rail-star { width: 50px; height: 50px; fill: var(--bg-0); }

/* The hairline joining them, and the only line left on this object. It runs from
   under the balance down the centre of the service tiles, so a service reads as
   a draw against the balance rather than as a bullet beside it. Drawn on the
   list, so it is one element and it ends with the last row whatever the row
   count is. Half the track: the tiles are centred in that track, so this cannot
   drift away from them. */
.rail-list { position: relative; }
.rail-list::before {
  content: ""; position: absolute; width: 1px; background: rgb(var(--accent-rgb) / .28);
  left: calc(var(--rail-w) / 2); top: calc(var(--step-6) * -1); bottom: 21px;
}
.rail-row .rail-tile { position: relative; background: var(--bg-0); }

/* One sequence on arrival: the balance, then each service under it. It runs
   once and stops. An ambient loop under a headline is movement competing with
   the thing somebody came to read.
   The stagger is held here and not in the markup: this page sends no nonce, so
   a `--i` set as an attribute is a `--i` that never arrives, and one undefined
   property voids the whole calc() and lands every row at once. */
.rail-row:nth-child(1) { --i: 1; }
.rail-row:nth-child(2) { --i: 2; }
.rail-row:nth-child(3) { --i: 3; }
.rail-row:nth-child(4) { --i: 4; }
.rail-row:nth-child(5) { --i: 5; }
.rail-top { opacity: 0; animation: rail-in 620ms cubic-bezier(.16,.84,.44,1) forwards; }
.rail-row {
  opacity: 0; animation: rail-in 520ms cubic-bezier(.16,.84,.44,1) forwards;
  animation-delay: calc(90ms * var(--i) + 260ms);
}
.rail-list::before {
  transform-origin: top; transform: scaleY(0);
  animation: rail-drop 620ms cubic-bezier(.16,.84,.44,1) 220ms forwards;
}
@keyframes rail-in { from { transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes rail-drop { to { transform: scaleY(1); } }

@media (prefers-reduced-motion: reduce) {
  .rail-top, .rail-row, .rail-list::before {
    animation: none; opacity: 1; transform: none;
  }
}

/* The hero object: the three services as one settled order. It is the product,
   drawn as the artifact the product actually produces, rather than a stock
   illustration of nothing. -------------------------------------------------- */

.ledger {
  position: relative; margin: var(--step-16) auto 0; max-width: 940px; text-align: left;
  border: 1px solid var(--border-str); border-radius: var(--r-md);
  background: var(--plate-ledger);
  box-shadow: 0 1px 0 rgba(255,255,255,.06) inset, 0 30px 80px rgba(0,0,0,.55);
  overflow: hidden;
}
.ledger-bar, .ledger-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--step-4) var(--step-6);
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-3);
  background: rgba(255,255,255,.022);
}
.ledger-state { color: var(--accent); }
.ledger-rows { padding: var(--step-2) 0; }
.lrow {
  display: grid; grid-template-columns: 10px 64px 110px 1fr auto;
  gap: var(--step-4); align-items: center;
  padding: var(--step-4) var(--step-6); font-size: 13px; color: var(--text-2);
}
.lrow .pip { width: 6px; height: 6px; border-radius: var(--r-pill); background: var(--accent); box-shadow: 0 0 8px rgb(var(--accent-rgb) / .6); }
.lrow .pip[data-s="run"] { background: var(--warn); box-shadow: 0 0 8px rgb(var(--warn-rgb) / .55); }
.lk { color: var(--text-3); }
.lv { color: var(--text-1); }
.lto { color: var(--text-3); }
.lamt { color: var(--text-1); font-size: 14px; }
.ledger-foot { border-top: 1px solid var(--border); }
.ledger-total { color: var(--text-1); font-size: 15px; letter-spacing: 0; }

@media (max-width: 720px) {
  .lrow { grid-template-columns: 10px 1fr auto; row-gap: var(--step-1); }
  .lto { display: none; }
  .lv { grid-column: 2; }
}

/* The policies document: a contents list that follows you down the page, and
   the clauses beside it. Two columns and not one, because eighteen sections is
   a length somebody arrives at wanting section eleven, and a page that can only
   be read from the top is a page that gets read from none of it. */
/* The body track is sized to the MEASURE, not to what is left over. `1fr` took
   the whole remaining 920px at 1180 while `.doc-body` capped its text at 68ch,
   so every one of the eighteen sections stopped two thirds of the way across
   and left a 355px gutter running the length of the longest page on the site -
   and the `space-between` rows inside it ended in mid-page too. Sizing the
   track to the measure and centring the pair puts that space on both sides. */
.doc { display: grid; grid-template-columns: 220px minmax(0, 68ch); gap: var(--step-10); align-items: start; justify-content: center; }
.doc-date { margin-top: var(--step-4); color: var(--text-3); font-size: 13px; }
.doc-toc { position: sticky; top: 88px; }
.doc-toc ol {
  list-style: none; margin: var(--step-4) 0 0; padding: 0;
  display: grid; gap: var(--step-2); counter-reset: toc;
}
.doc-toc li { counter-increment: toc; }
.doc-toc a {
  display: grid; grid-template-columns: 22px 1fr; gap: var(--step-2);
  font-size: 13px; line-height: 1.35; color: var(--text-3);
}
/* The number comes from the list and not from the words, so a section renamed
   or one withheld from a reader cannot leave the contents counting wrong. */
.doc-toc a::before {
  content: counter(toc); font-family: var(--font-mono); font-size: 11px;
  /* No `opacity` here. The rule above says this number is generated so a
     renamed section cannot leave the contents counting wrong - which makes
     it content, and .6 of --text-3 put content at 2.62:1. */
  color: var(--text-3); padding-top: 2px;
}
.doc-toc a:hover { color: var(--text-1); }
.doc-toc a:hover::before { opacity: 1; color: var(--accent); }

.doc-body { display: grid; gap: var(--step-12); max-width: 68ch; }
.doc-s { display: grid; gap: var(--step-4); scroll-margin-top: 96px; }
.doc-s h2 { font-size: 24px; }
.doc-s p { color: var(--text-2); font-size: 15px; line-height: 1.65; }
.doc-s .lede { color: var(--text-1); }
.doc-s .rowlist { margin: var(--step-2) 0; }

/* One column on a narrow screen, and the contents list becomes a plain block at
   the top instead of a rail that sticks to nothing. */
@media (max-width: 900px) {
  .doc { grid-template-columns: 1fr; gap: var(--step-8); }
  .doc-toc { position: static; }
  .doc-toc ol { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* And one column on a phone. Two columns of eighteen short links is two columns
   of wrapped words at this width, which is harder to scan than the list. */
@media (max-width: 560px) {
  .doc-toc ol { grid-template-columns: 1fr; }
}

/* The funnel table. Plain, because it is read by one person looking for one
   number and nothing on it is trying to sell anything. */
.fnl { width: 100%; border-collapse: collapse; margin: var(--step-4) 0 var(--step-10); }
.fnl th, .fnl td { text-align: left; padding: var(--step-3) var(--step-4); border-bottom: 1px solid var(--border); }
.fnl thead th { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-3); }
.fnl tbody th { font-weight: 400; color: var(--text-2); font-family: var(--font-mono); font-size: 13px; }
.fnl td { color: var(--text-1); font-size: 14px; }
.fnl td:last-child { color: var(--accent); }
/* A refusal to print a figure, or a qualifier on one that is printed, so neither
   may read as the figure itself: quieter than the accent the kept column is
   drawn in, and after that rule so it wins. Both selectors carry the element,
   because `.fnl .na` alone loses to `td:last-child` on specificity. */
.fnl td.na, .fnl td .na { color: var(--text-3); font-size: 12px; }

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

/* Uneven on purpose. Equal padding on every section makes every gap exactly
   the same, and a repeating interval is the thing the eye reads as generated. */
.section { position: relative; padding: var(--step-20) 0 var(--step-16); }
.section:nth-of-type(even) { padding: var(--step-12) 0 var(--step-24); }
main > .section + .section { margin-top: calc(var(--step-10) * -1); }

/* Opt-out for a page whose sections are structurally IDENTICAL. Varied padding
   only reads as deliberate when the sections it separates are also varied; on
   /policies every section is the same pair of panels with no head, so the rule
   above produced 162px between the first pair and the second and 72px between
   the second and the third, which reads as a spacing bug and was reported as
   one. A run of identical groups gets an even interval. */
.section-run, .section-run:nth-of-type(even) { padding: var(--step-16) 0; }
main > .section-run + .section-run { margin-top: 0; }

/* A band whose edges fade out past its own box, so it has no visible start or
   end. Explicit rgba zero rather than transparent: transparent is premultiplied
   black and ghosts grey along the ramp. */
.section-lit::before {
  content: ""; position: absolute; inset: -22vh 0; pointer-events: none;
  background: linear-gradient(180deg,
    rgba(255,255,255,0), rgba(255,255,255,.02) 34%,
    rgba(255,255,255,.02) 66%, rgba(255,255,255,0));
}

/* The bleed above exists so a join reads as light. The last band has no join
   below it, and its 22vh of downward bleed lands past the footer: 22vh is 154px
   at a 700px viewport, the footer covers 108px of that, and the browser has to
   reserve scroll space for the remaining 46px. The page then ends in a blank
   band that nothing paints, which reads as the layout being broken.

   Only the overhang goes. The gradient's last stop is already fully
   transparent, so there was never anything drawn in the part being removed. */
main > .section-lit:last-child::before { bottom: 0; }

/* The seam itself is the brightest point, so a join reads as light rather than
   as the place two blocks meet. */
main > .section + .section::after {
  content: ""; position: absolute; pointer-events: none;
  left: 50%; top: 0; transform: translate(-50%, -50%);
  width: min(1400px, 120vw); height: 520px;
  background: radial-gradient(50% 50% at 50% 50%, rgb(var(--accent-rgb) / .045), rgb(var(--accent-rgb) / 0) 70%);
}
.section > .wrap { position: relative; z-index: 1; }

/* The deck column is sized to the deck, not the other way round. It used to be
   a 1fr track with a 64px gutter — about 519px of reserved width — holding one
   sentence at 16px, bottom-aligned against a 50px headline. A line of small
   type stranded in half the page reads as a mistake however carefully it is
   set, and the same shape repeated on every section head and page head, which
   is what made the whole site feel loose. Narrower track, smaller gutter, and
   type with enough weight to hold the space it is given. */
.section-head {
  display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.85fr);
  column-gap: var(--step-10); align-items: end; margin-bottom: var(--step-10);
}
.section-head .eyebrow { grid-column: 1; grid-row: 1; }
.section-head h2 { grid-column: 1; grid-row: 2; font-size: clamp(32px, 4.2vw, 50px); margin-top: var(--step-4); }
.section-head p {
  grid-column: 2; grid-row: 2; color: var(--text-2);
  font-size: 19px; line-height: 1.5; max-width: 40ch; padding-bottom: 6px;
}

/* bento ------------------------------------------------------------------ */

/* Uneven spans. Cards of identical width say the products are
   interchangeable, and they are not: gifting is what most people arrive for.
   Seven and five, then five and seven, so neither row has a hole in it. */
.bento { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--step-4); }
.tile { grid-column: span 5; }
.tile-lead { grid-column: span 7; }
.tile {
  position: relative; display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background:
    linear-gradient(180deg, rgba(255,255,255,.035), rgba(255,255,255,0) 34%),
    var(--surface-1);
  box-shadow: var(--shadow-raised);
  padding: var(--step-6); overflow: hidden;
  transition: background 180ms ease-out, border-color 180ms ease-out, transform 180ms cubic-bezier(.16,.84,.44,1);
}
.tile:hover { background: var(--surface-2); border-color: rgb(var(--accent-rgb) / .30); transform: translateY(-2px); }
.tile-icon {
  width: 40px; height: 40px; display: grid; place-items: center; color: var(--accent);
  border-radius: var(--r-md); margin-bottom: var(--step-6);
  background: linear-gradient(180deg, rgb(var(--accent-rgb) / .14), rgb(var(--accent-rgb) / .04));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10), inset 0 0 0 1px rgb(var(--accent-rgb) / .18);
}
.tile h3 { font-size: 22px; }
.tile p { color: var(--text-2); margin-top: var(--step-3); font-size: 17px; line-height: 1.5; }
.tile .meta {
  margin-top: auto; padding-top: var(--step-5);
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--step-3);
}
/* The one figure on a tile takes that service's hue. It is the thing the eye
   lands on and it is information, so the colour is carrying the same signal as
   the icon rather than being spent on decoration. */
.tile .meta .mono { font-size: 15px; color: var(--accent); }
.tile .meta small { color: var(--text-3); font-size: 12px; }

.tile-lead h3 { font-size: 30px; }
.tile-lead p { font-size: 17px; max-width: 36ch; }
.tile-lead .meta .mono { font-size: 32px; letter-spacing: -0.03em; }

.tile-wide:hover { background: var(--surface-1); border-color: var(--border); }
.tile-wide {
  grid-column: span 7; padding: var(--step-8);
}
.tile-wide h3 { font-size: 26px; max-width: 20ch; }
.tile-wide p { max-width: 46ch; margin-top: var(--step-4); }
/* The grid is four tiles wide across two rows, and it is three when trading is
   not drawn. Without this the last tile keeps its seven columns and leaves five
   empty ones beside it, which reads as a tile that failed to load. */
.tile-full { grid-column: span 12; }

/* stepped rail: one continuous line, nodes on it, no card chrome ---------- */

.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--step-8); position: relative; }
.steps::before {
  content: ""; position: absolute; left: 15px; right: 15px; top: 17px; height: 1px;
  background: linear-gradient(90deg, var(--border-str), rgb(var(--accent-rgb) / .5));
}
.step { position: relative; padding-right: var(--step-6); }
.step-n {
  position: relative; z-index: 1;
  width: 34px; height: 34px; border-radius: var(--r-pill);
  display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 13px; font-weight: 600;
  background: var(--bg-1); color: var(--text-2);
  border: 1px solid var(--border-str); box-shadow: var(--shadow-inset);
  margin-bottom: var(--step-6);
}
.step:last-child .step-n {
  background: linear-gradient(180deg, var(--accent-lift), var(--accent));
  color: var(--accent-ink); border-color: transparent;
  box-shadow: 0 0 0 5px rgb(var(--accent-rgb) / .10);
}
.step h3 { font-size: 20px; }
.step p { color: var(--text-2); font-size: 16px; line-height: 1.5; margin-top: var(--step-3); }

/* editorial stack: heading holds its place while the points scroll past ---- */

.stack { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.5fr); gap: var(--step-16); }
.stack-head { position: sticky; top: 110px; align-self: start; }
.stack-head h2 { font-size: clamp(32px, 4.2vw, 50px); margin-top: var(--step-4); }
.stack-head p { color: var(--text-2); font-size: 16px; margin-top: var(--step-5); max-width: 40ch; }

/* A 20px glyph in a 52px column, twice the height of the line it sat beside,
   was spending the most space on the least information. The label is the icon
   now: it names the row, which a shield outline never did. */
.stack-items { display: grid; border-top: 1px solid var(--border); }
.stack-item {
  display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: var(--step-8);
  padding: var(--step-8) 0; border-bottom: 1px solid var(--border);
}
/* Plate first, then the word. The label alone left this column reading as a
   stray caption against a 24px heading; the icon gives the left side something
   to weigh against the right, which is what the column was for. */
.stack-key {
  display: flex; align-items: center; gap: var(--step-4);
  font-size: 12px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--text-3); padding-top: 2px;
  /* .mono carries overflow-wrap: anywhere for long hashes. In a fixed width
     column that turns a one word label into three lines. */
  overflow-wrap: normal;
}
/* Sized against the 24px heading opposite, not against the other icons on the
   page. A 20px glyph in a 44px plate read as a footnote beside that heading;
   an icon has to run a little OVER cap height to carry the same weight, so the
   glyph is 30px and the plate is scaled to keep its old padding ratio. */
.stack-ic {
  flex: none; width: 56px; height: 56px; display: grid; place-items: center;
  border-radius: var(--r-md); color: var(--accent);
  background: linear-gradient(180deg, rgb(var(--accent-rgb) / .14), rgb(var(--accent-rgb) / .04));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10), inset 0 0 0 1px rgb(var(--accent-rgb) / .18);
  transition: transform 160ms ease-out, box-shadow 160ms ease-out;
}
/* Thinner stroke at the larger size: 1.6px scaled up reads as a heavy blob. */
.stack-ic .ic { width: 30px; height: 30px; stroke-width: 1.45; }
.stack-item:hover .stack-ic {
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), inset 0 0 0 1px rgb(var(--accent-rgb) / .34);
}
.stack-item h3 { font-size: 24px; }
.stack-item p { color: var(--text-2); font-size: 16px; margin-top: var(--step-3); max-width: 56ch; }

/* a two option comparison, and a diagnostic list ------------------------- */

/* The 30% choice was four rowlist lines reading "You cover it / 1,000 lists at
   1,429 R$ / We receive / 1,000 R$", which is a table of a comparison rather
   than the comparison. Two cards put the alternatives beside each other, which
   is the shape the decision actually has, and they fill a panel that was
   holding twelve words across 660px. */
.choice { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--step-4); margin-top: var(--step-6); }
.opt {
  display: flex; flex-direction: column; gap: var(--step-3);
  padding: var(--step-5); border-radius: var(--r-md);
  border: 1px solid var(--border); background: rgba(255,255,255,.022);
  box-shadow: var(--shadow-inset);
}
.opt-k { font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-3); }
.opt p { font-size: 16px; line-height: 1.5; color: var(--text-2); }
.opt b { color: var(--text-1); font-weight: 600; }
.opt-out {
  margin-top: auto; padding-top: var(--step-3); border-top: 1px solid var(--border);
  font-family: var(--font-mono); font-size: 15px; color: var(--accent);
}
/* The same card made the link itself, for a comparison whose options are places
   to go rather than things to read. A card whose only target is three words at
   the bottom makes an easy click into a small one. */
/* A whole card that is a link, and an inset well with nothing raised about
   it, so the border is the affordance. The hover step used to be --border
   to --border-str, which is 1.1:1 to 1.2:1 - a state change nobody could
   see, on the control it was the only signal for. */
a.opt { text-decoration: none; border-color: var(--border-int); }
a.opt:hover { border-color: rgb(var(--accent-rgb) / .7); }

/* Symptom, then what to do about it. The old version was a rowlist whose right
   column repeated the same short instruction with no room to say why, and why is
   the whole point: the advice is worth nothing to somebody who does not know
   which symptom they have. */
.diag { list-style: none; margin: var(--step-6) 0 0; padding: 0; display: grid; gap: var(--step-2); }
.diag li {
  display: grid; grid-template-columns: 10px minmax(0, 1fr); gap: var(--step-4);
  padding: var(--step-4) 0; border-top: 1px solid var(--border);
}
.diag li:first-child { border-top: 0; }
.diag .pip {
  width: 8px; height: 8px; margin-top: 8px; border-radius: var(--r-pill);
  background: var(--idle); box-shadow: 0 0 8px rgb(var(--idle-rgb) / .5);
}
.diag .pip[data-s="run"] { background: var(--accent); box-shadow: 0 0 8px rgb(var(--accent-rgb) / .6); }
.diag .pip[data-s="warn"] { background: var(--warn); box-shadow: 0 0 8px rgb(var(--warn-rgb) / .55); }
.diag b { display: block; font-size: 17px; color: var(--text-1); font-weight: 600; }
.diag p { font-size: 16px; line-height: 1.5; color: var(--text-2); margin-top: var(--step-2); }

/* Eight chains down one column made a panel twice the height of the one beside
   it, holding two words per row. Two columns halve it and use the width. */
.mins { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 0 var(--step-6); margin-top: var(--step-6); }
.mins > div {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--step-3);
  padding: var(--step-3) 0; border-bottom: 1px solid var(--border);
  font-size: 16px; color: var(--text-2);
}
.mins dt, .mins dd { margin: 0; }
.mins .mono { color: var(--text-1); }

/* closing line, deliberately not another card ------------------------------ */

.band { display: flex; align-items: baseline; justify-content: space-between; gap: var(--step-8); flex-wrap: wrap; }
.band h2 { font-size: clamp(30px, 4vw, 46px); max-width: 20ch; }
.band p { color: var(--text-2); margin-top: var(--step-3); max-width: 44ch; }

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

.site-foot {
  margin-top: calc(var(--step-16) * -1);
  padding: var(--step-20) 0 var(--step-12);
  background: linear-gradient(180deg, rgba(0,0,0,0), rgba(0,0,0,.30));
}
.site-foot .wrap { display: flex; gap: var(--step-6); flex-wrap: wrap; align-items: center; }
.site-foot a { display: inline-flex; align-items: center; min-height: 44px; padding-inline: var(--step-2); }
.site-foot a, .site-foot span { color: var(--text-3); font-size: 14px; }
/* `--text-1`, because `--text-3` to `--text-2` is a 1.30:1 change - a state you
   cannot see. The footer is the site's ONLY navigation, so this is every nav
   link on every page, and `a[aria-current]` above already uses `--text-1` to say
   "this one". Same complaint the `a.opt` rule makes about itself. */
.site-foot a:hover { color: var(--text-1); }

