/* ═══════════════════════════════════════════════════════════════════════════════
   monitorMyLawn — PRODUCT PAGES  (/features, /tools, /how-it-works)
   Loaded as  $pageCss = ['marketing', 'product-pages'].

   This file adds ONLY what marketing.css does not already provide. Everything
   else on these three pages composes .plate / .vizframe / .hairgrid / .cmp /
   .callout / .specgrid / .toolindex / .section--dark from the design system.

   NO HEX LITERALS. tokens.css is the only place a hex may live (hard rule 1).
   Every colour below is a var(--mml-*) that tokens.css defines in all three
   theme blocks, so light and dark are covered without touching this file.

   ─────────────────────────────────────────────────────────────────────────────
   WHY .rise EXISTS  (read before replacing it with .reveal)
   ─────────────────────────────────────────────────────────────────────────────
   marketing.css's .reveal starts at opacity:0 under prefers-reduced-motion:
   no-preference and is only made visible when JavaScript adds .is-in. The only
   file that does that is assets/js/home.js, which is loaded by pages/home.php
   alone and is full of home-specific drawing code. These three pages ship no
   JS at all, so using .reveal here would render them permanently BLANK for
   every visitor who has not asked for reduced motion.

   .rise is the same effect with no JS. It is layered so that every single
   fallback path ends with the content VISIBLE:
     · reduced motion            → the @media never matches. Static, opacity 1.
     · no animation-timeline     → the @supports never matches. Static, opacity 1.
     · timeline never activates  → .rise's own `opacity:1` base still applies.
     · everything supported      → a scroll-progress fade/rise as it enters.
   The animation is progress-driven (animation-timeline replaces the clock), so
   the duration is nominal and it can never "finish" in a hidden state.
   ═══════════════════════════════════════════════════════════════════════════ */

.rise { opacity: 1; }

@keyframes pp-rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .rise {
      animation: pp-rise 1s linear both;
      animation-timeline: view();
      animation-range: entry 2% cover 24%;
      will-change: opacity, transform;
    }
    /* Stagger siblings by widening the range rather than by delay — a delay has
       no meaning on a progress timeline. */
    .rise[data-d="1"] { animation-range: entry 2% cover 30%; }
    .rise[data-d="2"] { animation-range: entry 2% cover 36%; }
    .rise[data-d="3"] { animation-range: entry 2% cover 42%; }
    .rise[data-d="4"] { animation-range: entry 2% cover 48%; }
  }
}

/* ═══ PAGE MASTHEAD — GONE, SEE marketing.css .pagehero ═══════════════════════
   These three pages used to own a local masthead set: .pagehead (text only, capped
   at 68ch) + .pagehead__cta + .factline. At 1440px that left the right-hand 46% of
   every masthead empty, which is the exact defect Track H's shared .pagehero was
   built to close — so all three now compose .pagehero and its own
   .pagehero__actions / .pagehero__facts instead, and the local rules are deleted
   rather than left to rot.
     /features      slot (a) .airplate    — one uplink, all eight channels
     /how-it-works  slot (c) .pagehero__fig — the depth cross-section
     /tools         slot (b) .contents    — the on-this-page index of the eight
   DO NOT re-add a .pagehead here. If a masthead needs something .pagehero cannot
   do, that is a marketing.css conversation (see "handoff").

   The one survivor is the plain button row, still used by the closing
   .section--dark band on each page. Renamed, because "pagehead" no longer
   describes where it is used. */
.btnrow { display: flex; flex-wrap: wrap; gap: var(--sp-md); margin-bottom: var(--sp-xl); }

/* ═══ JUMP NAV ════════════════════════════════════════════════════════════════
   /features and /how-it-works are five and six plates; their .pagehero slot is
   spent on a data panel and a figure, so they keep this rail under the masthead.
   /tools does NOT use it — its index moved into the masthead slot, and shipping
   both would put the same eight anchors on the screen twice, 100px apart, and in
   the accessibility tree twice. */
.jumpnav { border-top: 1px solid var(--mml-rule); border-bottom: 1px solid var(--mml-rule); padding: var(--sp-md) 0; }
.jumpnav__l {
  display: block; font-family: var(--font-mono); font-size: .6rem; letter-spacing: .15em;
  text-transform: uppercase; color: var(--mml-text-3); font-weight: 700; margin-bottom: var(--sp-sm);
}
.jumpnav ol { display: flex; flex-wrap: wrap; gap: .4rem var(--sp-md); list-style: none; margin: 0; padding: 0; }
.jumpnav a {
  display: inline-flex; align-items: baseline; gap: .45rem;
  font-size: .85rem; color: var(--mml-text-2); text-decoration: none;
  border-bottom: 1px solid transparent; padding-bottom: 1px;
}
.jumpnav a:hover { color: var(--mml-link); border-bottom-color: currentColor; text-decoration: none; }
.jumpnav a b {
  font-family: var(--font-mono); font-size: .62rem; font-weight: 700; letter-spacing: .08em;
  color: var(--mml-text-3);
}
.jumpnav a:hover b { color: var(--mml-link); }

/* ═══ THE QUESTION LINE ═══════════════════════════════════════════════════════
   Every tool plate opens by naming the question a reader actually has, in their
   words. Serif italic so it reads as a voice, not as a heading. */
.qline {
  font-family: var(--font-serif); font-style: italic;
  font-size: clamp(1.1rem, 1rem + .55vw, 1.45rem); line-height: 1.4;
  color: var(--mml-text); max-width: 46ch; margin: 0 0 var(--sp-lg);
  padding-left: var(--sp-lg); border-left: 2px solid var(--mml-primary);
}

/* ═══ CODE BLOCK ══════════════════════════════════════════════════════════════
   Used once, on /how-it-works, to show the shape of an ingest payload. The
   contents are the real contract from API_CONTRACTS.md. */
.codeblock {
  margin: 0; padding: var(--sp-md) var(--sp-lg);
  background: var(--mml-surface-hover); border: 1px solid var(--mml-rule);
  border-radius: var(--r-lg); overflow-x: auto;
  font-family: var(--font-mono); font-size: .74rem; line-height: 1.75;
  color: var(--mml-text-2); -webkit-overflow-scrolling: touch;
}
.codeblock b { color: var(--mml-text); font-weight: 700; }
.codeblock i { font-style: normal; color: var(--mml-link); }
.codeblock u { text-decoration: none; color: var(--mml-text-3); }
.codeblock__h {
  display: flex; flex-wrap: wrap; gap: .3rem var(--sp-md); justify-content: space-between;
  align-items: baseline; margin-bottom: var(--sp-sm);
}

/* ═══ READS / RETURNS / NEEDS ═════════════════════════════════════════════════
   The three-column honesty block under each tool figure. It is a .hairgrid at
   heart; this just tunes the mono "input list" rhythm inside it. */
.io-k {
  display: block; font-family: var(--font-mono); font-size: .6rem; letter-spacing: .15em;
  text-transform: uppercase; color: var(--mml-text-3); font-weight: 700; margin-bottom: var(--sp-sm);
}
.io-list { list-style: none; margin: 0; padding: 0; font-size: .84rem; line-height: 1.5; color: var(--mml-text-2); }
.io-list li { position: relative; padding-left: 1rem; margin-bottom: .3rem; }
/* A RULE, NOT AN EM DASH. This marker used to be the character U+2014 painted in
   --mml-ink-dec, which measured 1.91:1 — and tokens.css says of that token, in
   capitals, that it is decorative only and that "if you are about to put GLYPHS in
   it, you want --mml-text-3". Recolouring the dash to --mml-text-3 would have made
   the marker the same ink as the text beside it (#5e6f65 vs #5b6b61) and it would
   have stopped reading as a marker at all.
   So it stops being a glyph. An empty box drawn 1px tall in the decorative ink is
   pixel-for-pixel the dash it replaces, is non-text (no contrast minimum applies to
   it, exactly like a native list bullet), and restores tokens.css's "ZERO text uses"
   invariant, which this rule was quietly the last violation of. */
.io-list li::before {
  content: ""; position: absolute; left: 0; top: .68em;
  width: .6rem; height: 1px; background: var(--mml-ink-dec);
}
.io-list li:last-child { margin-bottom: 0; }
.io-list b { color: var(--mml-text); font-weight: 650; }
.io-list .num { font-size: .82rem; }

/* ═══ WHAT IT WILL NOT DO ═════════════════════════════════════════════════════
   Every tool gets one. It is deliberately the most prominent thing on the plate
   after the figure — an honest limit is a selling point when nothing has
   shipped and there is no social proof to lean on. */
.limits { margin-top: var(--sp-xl); }
.limits ul { list-style: none; margin: var(--sp-sm) 0 0; padding: 0; }
.limits li {
  position: relative; padding-left: 1.5rem; margin-bottom: var(--sp-sm);
  font-size: .89rem; line-height: 1.6; color: var(--mml-text-2);
}
.limits li:last-child { margin-bottom: 0; }
.limits li::before {
  content: "\00D7"; position: absolute; left: .25rem; top: -.06em;
  color: var(--mml-viz-warn); font-weight: 800; font-size: 1.05em;
}
.limits strong { color: var(--mml-text); font-weight: 650; }

/* ── SAME CONTENT, SECOND CONTAINER  (.limits--ledger) ────────────────────────
   The limits blocks are the best thing on /tools and none of them is going away.
   What was wrong is that all eight arrived in the SAME amber box, so by the fifth
   one the reader's eye had learned to skip it. There are now two containers for
   one piece of content:
     · .callout--warn .limits   the amber panel — movements I and III
     · .limits--ledger          a hairline specification appendix — movement II
   The ledger drops the wash and the × bullet and numbers each entry LIMIT 01,
   02, … in the gutter, so it reads as part of the manual rather than as a warning
   sticker. It is no quieter: it runs the full measure instead of sitting inside a
   padded box.
   CONTRAST: the gutter label is --mml-viz-warn, the TEXT-safe member of the amber
   pair. Measured over both surfaces a movement-II plate can have —
   4.68:1 on --mml-bg / 5.05:1 on --mml-surface (light), 10.86:1 / 9.75:1 (dark).
   The × bullet's own colour is unchanged; this variant simply does not draw it. */
.limits--ledger { margin-top: var(--sp-3xl); }
.limits--ledger > .k {
  display: block; font-family: var(--font-mono); font-size: .6rem; letter-spacing: .14em;
  text-transform: uppercase; font-weight: 700; color: var(--mml-viz-warn);
  padding-bottom: var(--sp-sm); border-bottom: 1px solid var(--mml-rule-strong);
}
.limits--ledger ul { counter-reset: lim; margin: 0; }
/* ABSOLUTE GUTTER, NOT A GRID. `display:grid` on the <li> was a real, shipped-for-
   ten-minutes bug: every entry's inline <strong> became its own grid item, so
   "The leak rule wants **6 %VWC inside two hours**." rendered as two columns with
   the emphasis stacked in the gutter. These entries are ONE inline flow with
   emphasis in the middle of it; only .wontsay, whose entry is a single block child,
   can safely be a grid. */
.limits--ledger li {
  counter-increment: lim; margin: 0; position: relative;
  padding: var(--sp-md) 0 var(--sp-md) 5.2rem;
  border-bottom: 1px solid var(--mml-rule);
}
.limits--ledger li::before {
  content: "LIMIT " counter(lim, decimal-leading-zero);
  position: absolute; left: 0; top: calc(var(--sp-md) + .24em);
  font-family: var(--font-mono); font-size: .58rem; font-weight: 800; letter-spacing: .09em;
  color: var(--mml-viz-warn); white-space: nowrap;
}
@media (max-width: 560px) {
  /* 5.2rem of gutter is 21% of a 390px screen. The word "LIMIT" is the part that
     can go; the number is the part that is doing the work. */
  .limits--ledger li { padding-left: 2.1rem; }
  .limits--ledger li::before { content: counter(lim, decimal-leading-zero); }
}

/* ═══ MOVEMENTS — THE /tools INTERSTITIALS ════════════════════════════════════
   WHY THIS EXISTS. /tools was eight identical plates — figure, three-column io
   rail, amber limits panel — repeated over ~13,800px of desktop and ~19,200px of
   phone. Rigorous documentation, metronomic art direction: nothing ever changed
   shape, so there was no way to tell tool six from tool two at a glance and no
   sense of progress through the document.
   The eight are now three MOVEMENTS, each opened by one of these bands. A movement
   band is deliberately NOT a plate: no FIG rail, no figure, no io grid. It carries
   a display numeral in the plate rail's own 104px column, one sentence of why, and
   the tools inside it as anchors — so a reader can skip a whole movement rather
   than one tool at a time.
   The numeral is aria-hidden decoration; the .eyebrow above it carries "Part one"
   in text, so nothing is conveyed by the numeral alone. */
.movement { padding-block: calc(var(--section-pad) * .55); }
.movement__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-sm); min-width: 0; }
@media (min-width: 1080px) {
  /* Same 104px rail as .plate__grid, so the movement numeral lands on the column
     the FIG numbers occupy and the page keeps one left margin. */
  .movement__grid { grid-template-columns: 104px minmax(0, 1fr); gap: 0; }
}
.movement__body { min-width: 0; }
.movement__no {
  font-family: var(--font-mono); font-weight: 800; line-height: .82;
  font-size: clamp(2.2rem, 1.7rem + 1.8vw, 3.4rem); letter-spacing: -.04em;
  /* --mml-text-3, the same ink .plate__no uses for a FIG number, because this is
     the same thing: a part marker in the rail column. It was --mml-ink-dec, which
     measured 2.07:1 — under AA-large even for a decorative glyph, and the token is
     documented as non-text only. --mml-text-3 measures 4.94:1 on --mml-bg and
     5.33:1 on --mml-surface, and it does not compete with the h2 beside it because
     it lives 104px out in the margin.
     The numeral stays aria-hidden: its text twin ("Part one") is in the .eyebrow
     one line below, so announcing "I" would be noise, not information. */
  color: var(--mml-text-3); margin: 0;
}
.movement__t {
  font-size: clamp(1.5rem, 1.22rem + 1.15vw, 2.2rem); line-height: 1.1;
  letter-spacing: -.026em; font-weight: 800; text-wrap: balance;
  margin: 0 0 var(--sp-md);
}
.movement__why { color: var(--mml-text-2); font-size: clamp(1rem, .96rem + .22vw, 1.1rem); line-height: 1.6; margin: 0; }
.movement__list {
  list-style: none; margin: var(--sp-xl) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .4rem var(--sp-xl);
  border-top: 1px solid var(--mml-rule); padding-top: var(--sp-md);
}
.movement__list a {
  display: inline-flex; align-items: baseline; gap: .45rem; font-size: .88rem;
  color: var(--mml-text-2); border-bottom: 1px solid transparent; padding-bottom: 1px;
}
.movement__list a:hover { color: var(--mml-link); border-bottom-color: currentColor; text-decoration: none; }
.movement__list b {
  font-family: var(--font-mono); font-size: .64rem; font-weight: 800; letter-spacing: .08em;
  color: var(--mml-text-3);
}
.movement__list a:hover b { color: var(--mml-link); }

/* ═══ THE MID-PAGE BREAK  (.wontsay, inside a .section--dark) ═════════════════
   Placed immediately after FIG.03 — the point in the document the critique named
   as where the pattern has to break. It is the shortest section on the page, it
   carries no figure, no table and no io rail, and it is the only earth band before
   the close: five sentences the product refuses to say, each one traceable to a
   limit printed on a plate above or below it.
   Every colour here is an INK token, not a cream token, so .section--dark's own
   re-pointing does the inversion (COMPONENT INDEX, .section--dark). That also
   means the block would still be legible if it were ever used on a light band. */
.wontsay { list-style: none; margin: 0; padding: 0; counter-reset: ws; border-top: 1px solid var(--mml-rule); }
.wontsay li {
  counter-increment: ws; display: grid; grid-template-columns: 2.4rem minmax(0, 1fr);
  gap: var(--sp-md); align-items: baseline;
  padding: var(--sp-lg) 0; border-bottom: 1px solid var(--mml-rule);
}
.wontsay li::before {
  content: counter(ws, decimal-leading-zero);
  font-family: var(--font-mono); font-size: .68rem; font-weight: 800; letter-spacing: .08em;
  color: var(--mml-text-3);
}
.wontsay p { margin: 0; font-size: clamp(1.02rem, .96rem + .34vw, 1.2rem); line-height: 1.5; color: var(--mml-text); font-weight: 550; max-width: 64ch; }
.wontsay em {
  display: block; margin-top: .35rem; font-style: normal;
  font-family: var(--font-mono); font-size: .68rem; letter-spacing: .05em; line-height: 1.5;
  /* Capped in ch, not px: at .68rem mono an uncapped annotation ran the full 1130px
     of the band, which is ~145 characters a line — unreadable however good the ink. */
  color: var(--mml-text-2); max-width: 92ch;
}
@media (max-width: 560px) {
  .wontsay li { grid-template-columns: minmax(0, 1fr); gap: .3rem; }
}

/* A mono "you are here" line. Two of these mark the two structural breaks, which
   is the cheapest way to give a 13,800px document a sense of distance covered. */
.progressmark {
  display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .7rem; margin: 0 0 var(--sp-3xl);
  font-family: var(--font-mono); font-size: .63rem; letter-spacing: .14em;
  text-transform: uppercase; font-weight: 700; color: var(--mml-text-3);
}
.progressmark s { text-decoration: none; color: var(--mml-rule-strong); }

/* ═══ THE PROMOTED PLATE  (.tradeoff) ═════════════════════════════════════════
   FIG.02, the watering advisor, is by the page's own admission "the tool the
   product is built around" — and it was getting byte-for-byte the same template
   as the other seven, one beat in eight. It now runs in .container--wide and sets
   what it needs against where it fails side by side, so the document has one
   obvious centre of gravity instead of eight equal ones. */
.tradeoff { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-xl); min-width: 0; }
@media (min-width: 1000px) {
  .tradeoff { grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr); gap: var(--sp-3xl); align-items: start; }
}
.tradeoff > * { min-width: 0; }

/* ═══ AN UNFRAMED FIGURE  (.openfig) ══════════════════════════════════════════
   Seven of the eight tool figures sit in a .vizframe. The soil-temperature window
   chart is a single annotated axis rather than a panel of data, so the frame was
   adding a box and nothing else — and, at plate seven, one more identical box.
   Same head line, same .scroller escape hatch, no chrome. */
.openfig { margin: 0; min-width: 0; }
.openfig__h {
  display: flex; flex-wrap: wrap; gap: .3rem var(--sp-lg); align-items: baseline;
  justify-content: space-between; margin-bottom: var(--sp-md);
  padding-bottom: var(--sp-sm); border-bottom: 1px solid var(--mml-rule-strong);
}
.openfig svg { display: block; width: 100%; height: auto; }
.openfig__cap {
  font-family: var(--font-serif); font-style: italic; font-size: .88rem;
  color: var(--mml-text-3); line-height: 1.5; margin: var(--sp-md) 0 0; max-width: 74ch;
}

/* ═══ INLINE LINKS IN A FIGURE CAPTION — WCAG 1.4.1 ═══════════════════════════
   base.css ships `a { text-decoration: none }` and only underlines on hover, which
   is fine for a nav list but not for a link sitting inside a sentence: 1.4.1 lets
   colour stand alone only when the link ink and the surrounding ink differ by 3:1
   or more. Measured here, they do not come close —
     .plate__cap is --mml-text-3, the link is --mml-link:
       light  #1f6b39 on #5e6f65 = 1.23:1
       dark   #4caf6a on #8aa093 = 1.01:1
   which in dark is very nearly the SAME luminance. So the caption links carry a
   permanent underline, exactly as marketing.css already does for an inline link
   inside a .section--dark paragraph. Two captions use this today: /features Fig.02
   and /how-it-works Fig.05, both pointing at /tools. */
.plate__cap a,
.openfig__cap a { text-decoration: underline; text-underline-offset: .18em; }

/* ═══ .scroller EDGE-FADE PADS ON A BARE PLATE ════════════════════════════════
   marketing.css paints .scroller's two "cover" pads in --mml-surface and says so in
   its own comment: "a .scroller on a different background needs its own
   --scroller-bg". Inside a .vizframe that is exactly right, because the frame's fill
   IS --mml-surface. But three scrollers on these pages are direct children of a
   .plate__body — the two /features tables, the two /tools tables and the unframed
   FIG.07 figure — and a plain .plate is --mml-bg, so a 34px white pad was showing at
   each end as a pale vertical band against the grey.
   The custom property has to be re-declared ON the scroller: .scroller sets it on
   itself, and a value set on an ancestor loses to that. Hence the child selectors,
   which out-specify 0,1,0.
   No .section--dark case is needed — no bare scroller on these pages sits in an
   earth band, and .section--dark re-points --mml-surface anyway. */
.plate__body > .scroller,
.openfig .scroller { --scroller-bg: var(--mml-bg); }
.section--alt .plate__body > .scroller,
.section--alt .openfig .scroller { --scroller-bg: var(--mml-surface); }

/* ═══ FIGURE SIZING ═══════════════════════════════════════════════════════════
   Wide inline-SVG figures live in a .vizframe > .scroller and declare their own
   min-width, exactly as home.php does. These are the shared caps. */
.fig-wide { min-width: 720px; }
.fig-mid  { min-width: 560px; }
.fig-slim { min-width: 420px; }

/* ═══ COMPARISON TABLE, LAST COLUMN NOT SPECIAL ═══════════════════════════════
   .cmp tints its final column with rgba(--mml-primary-rgb, .05) and darkens its
   ink, because in its home use (home.php's "choosing a radio") the last column is
   the recommended option. None of the tables on these three pages has a winning
   last column — they end on "what reads it", "severity" and "limiting factor" —
   so the tint is semantically wrong here.
   It was also an ACCESSIBILITY failure: --mml-viz-warn on that tint measured
   4.40:1 behind the 10px "Warning" severity label, under the 4.5:1 AA floor. On
   the plain surface the same token measures 5.02:1. */
.cmp--plainlast td:last-child { background: none; color: var(--mml-text-2); }

/* ═══ .spec-tbc — AA REPAIR ═══════════════════════════════════════════════════
   marketing.css paints the UNCONFIRMED-hardware chip in --mml-viz-warn over an
   8% amber wash. Measured on the light theme, over a .plate background, that
   composite is #f4efe1 and the ink lands at 4.37:1 — under the 4.5:1 AA floor
   for an 11px label.
   Repair, keeping the chip just as loud:
     · drop the wash        — the dashed amber border already carries the alarm
     · ink --mml-warning-fg — #92660b light, #fcd34d dark
   Measured after the change: 4.71:1 light, 12.57:1 dark. The border stays
   --mml-viz-warn, which is non-text and only needs 3:1 (it has 4.65:1).
   NOTE FOR TRACK S: this is a defect in the shared component, so home.php's five
   chips are still at 4.37:1. Reported in handoff — fix belongs in marketing.css. */
/* Fixed at source in marketing.css — this override is now redundant and removed. */

/* ═══ METRIC LEGEND CHIPS ═════════════════════════════════════════════════════
   Used on the eight-channel figure so each row is identifiable in text as well
   as by colour (WCAG 1.4.1 — colour is never the only channel). */
.chankey { display: flex; flex-wrap: wrap; gap: .3rem var(--sp-md); list-style: none; margin: 0; padding: 0; }
.chankey li {
  display: inline-flex; align-items: center; gap: .4rem;
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .07em; color: var(--mml-text-3);
}
.chankey i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; flex: none; }

/* ═══ STEP / ROUTE CARDS ══════════════════════════════════════════════════════ */
.routehead { display: flex; align-items: center; gap: var(--sp-md); margin-bottom: var(--sp-sm); }
.routehead h3 { margin: 0; font-size: 1.08rem; letter-spacing: -.018em; }
.routehead .fa-solid, .routehead .fa-regular { color: var(--mml-link); font-size: 1.05rem; width: 1.2em; text-align: center; }

/* ═══ FIRST-WEEK GANTT ════════════════════════════════════════════════════════
   The honesty figure on /how-it-works: when each tool actually starts saying
   something. Bars are SVG; this is only the surrounding furniture. */
.wakekey { display: flex; flex-wrap: wrap; gap: .35rem var(--sp-lg); list-style: none; margin: var(--sp-md) 0 0; padding: 0; }
.wakekey li {
  display: inline-flex; align-items: center; gap: .45rem;
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--mml-text-3); font-weight: 650;
}
.wakekey i { width: 14px; height: 9px; border-radius: 2px; display: inline-block; flex: none; }

/* ═══ SMALL UTILITIES ═════════════════════════════════════════════════════════ */
.stack-lg > * + * { margin-top: var(--sp-lg); }
.stack-xl > * + * { margin-top: var(--sp-xl); }
.mt-2xl { margin-top: var(--sp-2xl); }
.mt-3xl { margin-top: var(--sp-3xl); }
.mb-0 { margin-bottom: 0; }
.figreset { margin: 0; }

/* A definition-style row used for the "what the setup screen hands you" list.
   ─────────────────────────────────────────────────────────────────────────────
   This lives inside .steptrack, which is a THREE-COLUMN track at >=900px — so
   each copy of it is only ~370px wide on a 1440px screen. A viewport media query
   here was wrong in exactly the way viewport queries usually are: it split the
   row into two columns because the WINDOW was wide, squeezing the value column
   to about 110px and setting the text one word per line.
   The layout depends on the width of the list, not the width of the window, so
   it is a container query. Stacked is the default, so a browser without
   container query support gets the safe form rather than the broken one. */
.handoff {
  list-style: none; margin: var(--sp-md) 0 0; padding: 0;
  border-top: 1px solid var(--mml-rule);
  container-type: inline-size;
}
.handoff li {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: .15rem;
  padding: .65rem 0; border-bottom: 1px solid var(--mml-rule);
}
@container (min-width: 30rem) {
  .handoff li { grid-template-columns: 11rem minmax(0, 1fr); gap: var(--sp-lg); align-items: baseline; }
}
.handoff .k {
  font-family: var(--font-mono); font-size: .63rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--mml-text-3); font-weight: 700;
}
.handoff .v { font-size: .89rem; line-height: 1.55; color: var(--mml-text-2); }
.handoff .v b { color: var(--mml-text); font-weight: 650; }

/* ═══ 390px GUARDS ════════════════════════════════════════════════════════════
   Nothing on these pages may push the body sideways. Wide figures scroll inside
   their own .scroller; these are the remaining offenders. */
@media (max-width: 460px) {
  .qline { padding-left: var(--sp-md); }
  .codeblock { font-size: .68rem; padding: var(--sp-sm) var(--sp-md); }
  .jumpnav ol { gap: .35rem var(--sp-md); }
  .btnrow .btn { width: 100%; justify-content: center; }
  /* The movement list is a wrapping row of eight-ish anchors; at 390px a 20px
     column gap turns it into a ragged block, so it becomes one anchor per line. */
  .movement__list { flex-direction: column; gap: .55rem; }
}
