/* ============================================================
   The phone layer
   ------------------------------------------------------------
   The rest of this site is drawn for a wide screen and a printed
   page: a nine-step type scale that reaches 9px, controls sized
   for a mouse, and tables eleven columns wide. None of that
   survives a thumb.

   This sheet is the same design at phone scale, not a shrunken
   copy of it. Three rules govern it:

     1. The type scale compresses rather than shrinks. A phone
        gets a floor of 14px; the sizes below that on a desktop
        (chips at 9.6px, control labels at 11.2px) collapse up to
        the floor and give back the width by tightening their
        letter-spacing instead.
     2. Every control is at least 44x44 to a thumb, measured on
        the box a finger can actually land on — which for a range
        input means the track's hit area, not the thumb's paint.
     3. Anything that needs more room than a phone has gets a
        surface of its own: wide tables scroll inside their box
        with the first column pinned, and explanations, drawers
        and detail cards arrive as bottom sheets rather than
        pushing the page around under the reader's finger.

   It is linked last on every page, after each page's own
   <style> block, so a phone rule beats the desktop rule it
   replaces without needing !important to do it.

   Every query below says `screen`. Chrome lays a printed page out
   in a viewport about 816px wide, so a bare `max-width: 900px`
   applies on paper: the first version of this sheet turned the
   guide's 17 printed pages into 42 and the appendices' 174 into
   376. Nothing here is for paper.
   ============================================================ */

/* ---------- 0. things that are true at every width ---------- */

:root { --sheet-ease: cubic-bezier(.22,.72,.3,1); }

/* A phone's URL bar and home indicator eat the viewport. */
@supports (padding: env(safe-area-inset-bottom)) {
  body { padding-bottom: 0; }
}

/* Touch feedback: a control a thumb is holding says so. Pointer-coarse
   only, so a mouse keeps its hover instead. */
@media screen and (hover: none) {
  button:active, .crow:active, .topnav a:active, .btn:active,
  .stops button:active, .presets button:active, summary:active {
    transform: scale(.985);
    transition: transform .06s ease;
  }
  /* iOS paints its own grey box over the design's own active state */
  button, a, summary, [role="button"] { -webkit-tap-highlight-color: rgba(24,92,51,.14); }
}

/* A drawer's summary is a flex row: the marker, the label, the "— what is in
   it" run, and the (i) button. A flex item will not shrink below its own
   longest word, so at 200% text the run pushed the (i) past the right edge of
   the page — found on `scaling.html` when the 200%-text check was extended
   from four pages to all nine (run 6). The run is allowed to shrink and break,
   and if the row still cannot hold everything the (i) drops to a line of its
   own rather than off the page. */
details.drawer > summary { flex-wrap: wrap; }
details.drawer > summary > * { min-width: 0; }

/* Focus is visible on every ground, not only the light one. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--ever);
  outline-offset: 2px;
  border-radius: 2px;
}
.instrument :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline-color: var(--inst-accent, #E3C387);
}

/* ============================================================
   1. THE BAR AND ITS MENU  (below 900px)
   ============================================================ */
@media screen and (max-width: 899.98px) {
  .topbar {
    position: sticky; top: 0; z-index: 60;
    background: var(--paper);
    border-bottom: 1px solid var(--line);
    padding: .3rem var(--pad);
    gap: .6rem;
    flex-wrap: nowrap;
  }
  .instrument .topbar { background: var(--inst-bg, #0B2116); }

  /* the wordmark alone on the left, at a size a thumb can hit */
  .wordmark {
    display: inline-flex; align-items: center;
    min-height: 44px; font-size: 15px; letter-spacing: .07em;
  }

  /* A labelled control, not a bare hamburger: the three rules and the
     word both, because "Menu" is the only thing on this bar that says
     where the other twelve pages went. It becomes "Close" when open. */
  .menu-btn {
    display: inline-flex; align-items: center; gap: .5rem;
    min-height: 44px; min-width: 44px;
    padding: 0 .85rem; font-size: 15px; border-radius: 5px;
    border-color: var(--line-strong);
  }
  .menu-btn::before {
    content: ""; width: 17px; height: 12px; flex: none;
    border-top: 2px solid currentColor; border-bottom: 2px solid currentColor;
    background: linear-gradient(currentColor, currentColor) center/100% 2px no-repeat;
    transition: transform .18s var(--sheet-ease);
  }
  .menu-btn[aria-expanded="true"] {
    background: var(--ever); color: var(--paper); border-color: var(--ever);
  }
  .instrument .menu-btn[aria-expanded="true"] {
    background: var(--inst-accent, #E3C387); color: var(--inst-bg, #0B2116);
    border-color: var(--inst-accent, #E3C387);
  }
  /* open, the three rules become a cross — a rotated single bar reads as a
     cursor, not as a way out */
  .menu-btn[aria-expanded="true"]::before {
    border-color: transparent;
    background:
      linear-gradient(currentColor, currentColor) center/100% 2px no-repeat,
      linear-gradient(currentColor, currentColor) center/2px 100% no-repeat;
    transform: rotate(45deg);
    width: 15px; height: 15px;
  }

  /* The list is a full-width sheet under the bar, not a floating card
     clipped by the window's right edge. */
  .topnav.open {
    display: flex; flex-direction: column; position: fixed;
    /* the wide bar's row rules, unset: `justify-content: flex-end` in a column
       taller than its box pushes the overflow off the TOP, which put the first
       row 14px above the sheet's own edge, and `align-items: center` would
       shrink each row to its label */
    justify-content: flex-start; align-items: stretch; gap: 0;
    top: var(--bar-h, 52px); left: 0; right: 0; bottom: 0;
    /* `bottom: 0` alone measures against the viewport a phone has when its
       URL bar is hidden, so on an iPhone the sheet's own last rows sit under
       the browser's chrome and its scroll cannot bring them up. `dvh` is the
       height the reader actually has right now; a browser without it ignores
       the line and keeps the old behaviour. */
    max-height: calc(100dvh - var(--bar-h, 52px));
    background: var(--paper); border: none; border-radius: 0;
    padding: .4rem 0 calc(1.4rem + env(safe-area-inset-bottom, 0px));
    overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
    box-shadow: none; z-index: 55;
    animation: navin .2s var(--sheet-ease) backwards;
  }
  /* A flex item shrinks to fit by default; these rows keep their 52px and let
     the sheet scroll instead. */
  .topnav.open > *, .topnav.open::before { flex: none; }
  .instrument .topnav.open { background: var(--inst-bg, #0B2116); }
  @keyframes navin { from { opacity: 0; transform: translateY(-6px); } }

  .topnav.open a {
    display: flex; align-items: center; justify-content: space-between;
    min-height: 52px; padding: 0 var(--pad);
    font-size: 17px; font-weight: 600; color: var(--ink);
    border-bottom: 1px solid var(--line);
    border-left: 4px solid transparent;
  }
  .instrument .topnav.open a { color: var(--inst-fg, #F4EFE1); border-bottom-color: var(--inst-line, rgba(231,226,211,.14)); }

  /* the page you are on, marked rather than merely underlined */
  .topnav.open a[aria-current="page"] {
    border-left-color: var(--ever); background: var(--paper-2);
    color: var(--ever); font-weight: 700;
  }
  .instrument .topnav.open a[aria-current="page"] {
    border-left-color: var(--inst-accent, #E3C387);
    background: rgba(227,195,135,.1); color: var(--inst-accent, #E3C387);
  }
  .topnav.open a[aria-current="page"]::after {
    content: "you are here"; font-family: var(--mono); font-size: 12px;
    font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
    color: var(--ink-faint); text-transform: uppercase;
  }
  .instrument .topnav.open a[aria-current="page"]::after { color: var(--inst-dim, #A9B3A2); }

  /* the destination that is a text link on a wide screen stays a text
     link here — it keeps its arrow and its green, and gains no box */
  .topnav.open a.nav-go { color: var(--ever); font-weight: 700; border-bottom-color: var(--line); }
  .instrument .topnav.open a.nav-go { color: var(--g-mini, #7BC97F); }

  /* a heading inside the sheet, so the twelve destinations read as a list
     of places rather than a dump of links */
  .topnav.open::before {
    content: "Go to";
    display: block; padding: .9rem var(--pad) .5rem;
    font-family: var(--mono); font-size: 14px; letter-spacing: .13em;
    text-transform: uppercase; color: var(--ink-faint);
  }
  .instrument .topnav.open::before { color: var(--inst-dim, #A9B3A2); }

  /* The way into the walkthrough, first in the sheet and shaped like every
     other row in it. It used to be the thirteenth row, below the bottom edge
     of an 844px screen — the owner reported it as unreachable on 2026-09-04.
     `order` beats its place in the source, which on a wide screen keeps it at
     the right-hand end of the bar. */
  .topnav.open .tour-pill {
    order: -1;
    display: flex; align-items: center; gap: .6rem;
    width: 100%; min-height: 52px; padding: 0 var(--pad);
    background: var(--paper-2); color: var(--ever);
    border: 0; border-bottom: 1px solid var(--line);
    border-left: 4px solid var(--ever); border-radius: 0;
    font-size: 17px; font-weight: 700; letter-spacing: 0;
    /* the pill's own `nowrap` would push the label out of the row's side at
       200% text; a row is allowed to grow downwards instead */
    text-align: left; white-space: normal;
  }
  .topnav.open .tour-pill .dot {
    width: 9px; height: 9px; margin: 0; flex: none; vertical-align: baseline;
  }
  .instrument .topnav.open .tour-pill {
    background: rgba(227, 195, 135, .1); color: var(--inst-accent, #E3C387);
    border-bottom-color: var(--inst-line, rgba(231, 226, 211, .14));
    border-left-color: var(--inst-accent, #E3C387);
  }

  body.nav-open { overflow: hidden; }
}

/* ============================================================
   2. THE TOUCH SCALE  (a phone, or any touch screen up to a tablet)
   ------------------------------------------------------------
   Type size and target size answer to the finger, not to the window,
   so this block reaches a 768px tablet as well as a 390px phone — and
   deliberately does not reach a narrow window on a laptop, where a
   mouse is still doing the pointing.
   ============================================================ */
@media screen and (max-width: 760px), screen and (pointer: coarse) and (max-width: 1024px) {

  /* ---- 2.1 the type floor ----
     Nothing a reader can see is under 14px. The chips and small-caps
     labels give the width back through their tracking, not their size.

     These carry !important because they are a floor, not a preference:
     the sizes they replace are set by two- and three-level selectors
     inside each page's own <style> block (`.ruler .readout .lab`,
     `.unit .mdl`, `table.cmp th`), and matching every one of those here
     would mean copying each page's DOM shape into a shared sheet and
     re-copying it whenever a page moved a div. A floor that any page
     can silently drop below is not a floor. */

  /* evidence chips and status chips */
  .tag, .grade, .wave, .hyb, .chip, .tierchip, .pin, .badge,
  .eyebrow, span.eyebrow, div.eyebrow, .nodecard h3 .eyebrow {
    font-size: 14px !important; letter-spacing: .05em !important;
    padding-left: .38em; padding-right: .38em;
  }
  .tag, .grade { vertical-align: 1px; }

  /* the small-caps name above every control, and the key under every figure */
  .kicker, .lab, .lbl, .olab, .sublab, div.grp, .k, span.cap,
  .ctl label, .ovr label, .assume label, .exact span,
  .stat .k, .tile .k, .keyrow .k, .minitab div, .glegend, .legend,
  .hm-row .row b, .ap-letter, .nodecard .proof b, .rung .at,
  .five-kicker, .five-k,
  th, .schools th, table.cmp th, .cfg th {
    font-size: 14px !important; letter-spacing: .05em !important;
  }
  .kicker { letter-spacing: .14em !important; }

  /* A tile's caption is set in caps, which is right for "Adoption fund" and
     wrong for the four that are whole sentences: "Who runs it — 1.25 of a
     full-time professional position, at the loaded Provo wage — real money,
     shown, never folded into the price of the machines" runs to ten lines of
     capitals on a phone, and capitals have no ascenders or descenders for the
     eye to read a line by. The words are already written in sentence case, so
     letting them through is the whole change; the label tracking goes with the
     capitals, because it only ever existed to open them up. */
  .tile .k { text-transform: none; letter-spacing: normal !important; }

  /* supporting prose: hints, notes, captions, provenance, source lines */
  .hint, .minihint, .note, .note-small, .runs, .meta, .mbuild, .sub, .role,
  .proof, .why, .foot, .src, .tfoot, .provenance, .scalenote,
  .sumline, .notice, .prep, .nm, .mdl, .tier, .sm, .small, .n, .pct,
  .rule, .checkline, .emptyfleet, .nochange, .dial, .at,
  .standing-notice, .hero-notice, .indep-notice, .story p, .risks li,
  .unit .nm, .unit .mdl, .unit .mdl s, .unit .role, .rung .why,
  .nodecard .sub, .nodecard .proof, .nodecard .hint, .rail-head .sub,
  .tile .v em, .stat .v small, .unit .n em,
  figcaption, caption, small, .concept .rationale, .concept .label span,
  .cover .sub, .cover .prep, .cover .notice {
    font-size: 14px !important;
  }
  .hpanel, .howto { font-size: 15px !important; line-height: 1.55; }

  /* A link inside a sentence is not given a box; it is given room. At a
     phone's line-height the underlines of two stacked lines sit close
     enough that a thumb lands between them. */
  p, li, dd, .tfoot, .src, .note, .hint, .minihint, .foot,
  .provenance, .runs, .meta, .why, .role, .story p { line-height: 1.72; }
  li { margin-bottom: .6em; }

  /* the last named few: a legend's own bold caption, the "at once" unit
     beside a figure, a reset button, and the exact-money box's label */
  .optkeys, .optkeys b, .opts .olab, .mrow .mname, .row .name, .num, span.num,
  .hero-side .row b, .kv, .kv span, .kv b, .bar, .sc-card .tier,
  .ap-card span, .ribbon span, .intro .back, .ckey, .legend-side span,
  .scalebox, .scalebox span,
  .legend b, .minitab b, .big em, .v em, .n em, .unit .n span,
  .drawerbody .reset, .reset, .keyrow em, .tile em, .facts span, .facts div,
  .stops button b, .concept .label b, .stat .v em { font-size: 14px !important; }
  .lede { font-size: 17px; }

  /* tables: the head is a label, the body is prose */
  td, .schools td, table.cmp td, .cfg td, table { font-size: 15px !important; }
  td.small, table.cmp td.small, .cfg td.small { font-size: 14px !important; }
  pre, pre.mono, code, .mono.block { font-size: 14px !important; }

  /* the walkthrough's own offer and cards */
  .tour-offer .go, .tour-offer .no, .tour-card .go, .tour-card .no,
  .tour-offer p, .tour-card p { font-size: 15px !important; }

  /* The (i) disc is a 20px glyph on a desktop. On a touch screen it is
     both the icon and the target, so it grows with everything else. */
  .hbtn { width: 26px; height: 26px; vertical-align: -6px; }
  .hbtn > span[aria-hidden] { font-size: 15px !important; }

  /* ---- 2.1b charts keep their type and scroll instead ----
     An SVG with a viewBox does not shrink its labels' font-size; it
     shrinks the whole drawing, so a 12px axis label squeezed into a
     360px phone reaches the eye at 4px. The chart keeps the width it
     was drawn for and scrolls inside its own box. */
  .chartwrap, figure:has(> svg[viewBox]) {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    /* the same edge cue the wide tables get */
    scroll-snap-type: x proximity;
  }
  .chartwrap > svg[viewBox] { min-width: var(--chart-min, 0); max-width: none; }

  /* ---- 2.2 tap targets ----
     44x44 on everything a thumb aims at. */

  .seg button, .seg2 button, .switch button, .modes button,
  .presets button, .stops button, .stepper button,
  button.go, button.no, button.reset, .crow, summary,
  select, input[type="number"], input[type="text"], .exact input,
  .priceEdit, select.model {
    min-height: 44px;
  }
  .seg button, .seg2 button, .switch button, .modes button {
    padding-left: .9em; padding-right: .9em; font-size: 15px;
  }
  .presets button, .stops button { padding: 0 .95em; font-size: 14px; }
  .seg.tiny button { font-size: 15px; padding: 0 .8em; }
  .stepper button { min-width: 44px; font-size: 20px; }
  /* a standalone link is a destination, not a sentence */
  .intro .back, .ap-card, .toc a {
    display: inline-flex; align-items: center; min-height: 44px;
  }
  .ap-card { display: flex; flex-direction: column; justify-content: center; }
  /* a two-character choice ("1 TB") is still a 44px-wide target */
  .seg button, .seg2 button, .switch button, .modes button,
  .presets button, .stops button { min-width: 44px; }
  select, select.model, input[type="number"], input[type="text"], .priceEdit {
    font-size: 16px;    /* under 16px, iOS zooms the page on focus */
    padding: .5em .7em;
  }
  .exact { min-height: 46px; align-items: center; }
  /* 16px keeps iOS from zooming on focus, but the box is border-box and the
     44px tap padding eats 22px of it, so 8ch left room for five digits and
     clipped the last one off $120,000. Eleven buys the six digits the slider
     can reach, and a little slack. */
  .exact input { font-size: 16px; width: 11ch; min-height: 44px; }
  summary { padding-top: .55rem; padding-bottom: .55rem; }

  /* a thumb-sized budget slider: a 28px thumb on a 44px hit area.
     The three pages that draw their own track style `.dial input[type=range]`
     and the like, which outranks a bare element selector, so they are named. */
  input[type="range"], .dial input[type="range"], .ovr input[type="range"],
  .assume input[type="range"] {
    -webkit-appearance: none; appearance: none;
    width: 100%; height: 44px; background: transparent;
    margin: 0; padding: 0;
  }
  input[type="range"]::-webkit-slider-runnable-track,
  .dial input[type="range"]::-webkit-slider-runnable-track,
  .ovr input[type="range"]::-webkit-slider-runnable-track,
  .assume input[type="range"]::-webkit-slider-runnable-track {
    height: 8px; border-radius: 4px;
    background: rgba(231,226,211,.22);
  }
  input[type="range"]::-webkit-slider-thumb,
  .dial input[type="range"]::-webkit-slider-thumb,
  .ovr input[type="range"]::-webkit-slider-thumb,
  .assume input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 28px; height: 28px; margin-top: -10px;
    border-radius: 50%; background: var(--inst-accent, #E3C387);
    border: 2px solid var(--inst-bg, #0B2116);
    box-shadow: 0 1px 6px rgba(0,0,0,.5);
    cursor: pointer;
  }
  input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.08); }
  input[type="range"]:focus-visible::-webkit-slider-thumb {
    outline: 3px solid var(--inst-accent, #E3C387); outline-offset: 2px;
  }
  /* the two pages that draw their own track behind a transparent input
     keep it: the input is the hit area, the drawing is the track */
  .track input[type="range"], .t-track input[type="range"],
  .ruler input[type="range"] { height: 44px; }
  .track input[type="range"]::-webkit-slider-runnable-track,
  .t-track input[type="range"]::-webkit-slider-runnable-track,
  .ruler input[type="range"]::-webkit-slider-runnable-track { background: transparent; }

  /* a checkbox is 13px of paint; the label beside it makes the target */
  input[type="checkbox"], input[type="radio"],
  .checkline input, .ovr input[type="checkbox"] {
    width: 24px; height: 24px; flex: none;   /* a flex row must not shrink it */
  }
  .checkline, label:has(> input[type="checkbox"]) {
    min-height: 44px; display: flex; align-items: center; gap: .55rem;
  }

  /* Rows of proof links read as a sentence, so they stay inline — but
     each one gets the vertical room a thumb needs to land on it alone. */
  .proof a, .hint a, .foot a, .note a, .minihint a, .runs a {
    display: inline-flex; align-items: center; min-height: 44px;
    padding: 0; line-height: 1.2;
  }

  /* the map's own reset, added by mobile.js */
  .map-reset {
    position: absolute; right: .6rem; bottom: .6rem; z-index: 4;
    min-height: 44px; padding: 0 1rem; border-radius: 6px;
    font-family: var(--sans); font-size: 15px; font-weight: 600;
    color: var(--inst-fg, #F4EFE1);
    background: rgba(11,33,22,.82); border: 1px solid rgba(231,226,211,.3);
    cursor: pointer; display: none;
  }
  .mapbox.zoomed .map-reset { display: block; }

  /* ---- 2.4 wide tables: scroll inside the box, first column pinned ----
     A table wider than the phone gets its own scroller, a pinned row
     label so a reader who has scrolled to column nine still knows which
     row they are on, and an edge shadow that says there is more. */
  .tablewrap, .scrollx {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    background-image:
      linear-gradient(to right, var(--card) 30%, rgba(252,251,248,0)),
      linear-gradient(to right, rgba(252,251,248,0), var(--card) 70%),
      linear-gradient(to right, rgba(22,26,23,.16), rgba(22,26,23,0)),
      linear-gradient(to left,  rgba(22,26,23,.16), rgba(22,26,23,0));
    background-position: 0 0, 100% 0, 0 0, 100% 0;
    background-repeat: no-repeat;
    background-size: 34px 100%, 34px 100%, 14px 100%, 14px 100%;
    background-attachment: local, local, scroll, scroll;
  }
  .instrument .scrollx, .instrument .tablewrap {
    background-image:
      linear-gradient(to right, var(--inst-panel, #10281B) 30%, rgba(16,40,27,0)),
      linear-gradient(to right, rgba(16,40,27,0), var(--inst-panel, #10281B) 70%),
      linear-gradient(to right, rgba(0,0,0,.4), rgba(0,0,0,0)),
      linear-gradient(to left,  rgba(0,0,0,.4), rgba(0,0,0,0));
  }

  /* the pinned row label */
  .tablewrap table th:first-child, .tablewrap table td:first-child,
  .scrollx table th:first-child, .scrollx table td:first-child {
    position: sticky; left: 0; z-index: 2;
    background: var(--card);
    box-shadow: 1px 0 0 var(--line);
    max-width: 11rem;
  }
  .instrument .scrollx table th:first-child,
  .instrument .scrollx table td:first-child {
    background: var(--inst-panel, #10281B);
    box-shadow: 1px 0 0 var(--inst-line, rgba(231,226,211,.18));
  }
  th, td { white-space: normal; }
  td.num, th.num { white-space: nowrap; }

  /* The width mobile.js hands a squeezed table (see its `tighten`): a column
     wide enough for three or four words a line rather than two. Three of them
     come to 456px against a 390px phone, so the table now overflows and takes
     the scroller and the pinned first column above; a two-column table fits
     and simply stops squeezing. */
  table.tight th, table.tight td { min-width: 9.5rem; }

  /* "Swipe" said once, above the table, rather than as a mystery shadow.
     Its room is reserved from the first paint and the line is drawn into
     that room, because a caption that appears once the page has measured
     its own tables would move every table under it — 0.12 of a screen on
     the configurator, which has three. */
  .tablewrap, .scrollx { position: relative; padding-top: 1.5rem; }
  .tablewrap[data-wide]::before, .scrollx[data-wide]::before {
    content: "Swipe the table sideways to see every column";
    position: absolute; top: 0; left: 0; right: 0;
    padding: .35rem .9rem 0;
    font-family: var(--sans); font-size: 13px; font-weight: 600;
    letter-spacing: .04em; color: var(--ink-faint);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .instrument .scrollx[data-wide]::before { color: var(--inst-dim, #A9B3A2); }

  /* The machine rows are written by script into an empty div, so the page
     paints without them and then takes 2,400px of content — which shoves
     the presets and the note under it clean off the screen. On a phone the
     presets go first, which is also the better reading order (start from a
     preset, then adjust the machines), and the rows become the last thing
     in the panel, where their arrival moves nothing.
     The heading over the machine counts travels with them, or it is left at
     the top of the panel with the next heading directly under it and nothing
     of its own; the same goes for the note that belongs to the presets. Every
     other note stays where it was written, so the models note is still under
     the models. */
  .panel:has(> #rows) { display: flex; flex-direction: column; }
  .panel:has(> #rows) > [data-help="counts"] { order: 1; }
  .panel:has(> #rows) > .optkeys { order: 2; min-height: 6.4rem; }
  .panel:has(> #rows) > #presets,
  .panel:has(> #rows) > .presets { order: 3; margin-bottom: .4rem; }
  .panel:has(> #rows) > #presets + .note { order: 4; }
  .panel:has(> #rows) > #rows { order: 5; }
  .mrow-top, .row { grid-template-columns: 1fr; }
  /* an `auto` grid track sizes to the menu's longest option, so a
     max-width of 100% is a percentage of a track already too wide */
  select.model { width: 100%; max-width: 100%; }

  /* On a wide screen a control's name and its figure share a line, with an
     em dash between them. On a phone the figure takes a line of its own,
     which leaves the dash hanging off the end of the name. Laying the label
     out as a wrapping row drops the figure to its own line and keeps the (i)
     beside it, instead of stranding the button on a third line of its own. */
  .ctl label .sep, .ctl .lbl .sep { display: none; }
  .ctl label:has(.val), .ctl .lbl:has(.val) {
    display: flex; flex-wrap: wrap; align-items: center; gap: 0 .5rem;
  }
  .ctl label:has(.val) .val, .ctl .lbl:has(.val) .val { margin-top: .15rem; }
  .ctl label:has(.val) > .hbtn, .ctl .lbl:has(.val) > .hbtn { margin-top: .15rem; }

  /* Each switch's explanation is written by script from the engine's own
     numbers, so the eleven of them arrive together and lift 348px of
     console. Reserving two lines apiece — which is what most of them come
     to — leaves a little slack under the short ones and takes most of the
     movement out of the long ones. */
  .bank .hint, .bank .minihint { min-height: 3.9em; }
  /* the two that are empty in the markup and written from the engine */
  #budgetSide, #audHint, #n512Elig { min-height: 3.6em; }

  /* segmented toggles wrap instead of clipping, at every touch width */
  .seg, .seg2, .switch, .modes { display: flex; flex-wrap: wrap; }
  .seg button, .seg2 button, .switch button, .modes button { min-width: max(44px, max-content); }
}

/* ============================================================
   3. THE PHONE'S LAYOUT  (760px and under)
   ============================================================ */
@media screen and (max-width: 760px) {

  /* a grid whose column minimum is wider than the phone pushes the whole
     page sideways; the minimum yields to the window instead */
  .gallery { grid-template-columns: 1fr; }
  .cB { grid-template-columns: 1fr; }
  /* a shape, not a minimum: a min-height lets the photograph resize the
     band when it arrives */
  .cB .photo { aspect-ratio: 16 / 7; min-height: 0; }
  .cB .photo img { width: 100%; height: 100%; object-fit: cover; }

  /* ---- 3.0 the guide's shell ---- */
  .stats { grid-template-columns: repeat(auto-fit, minmax(min(9.5rem, 100%), 1fr)); }
  .stat { padding: .95rem 1rem 1.05rem; }
  .stat .v { font-size: 1.5rem; }
  .grid2, .grid3 { grid-template-columns: 1fr; }
  .card { padding: 1rem 1.05rem; margin: 1rem 0; }
  /* the section number and its title share a row until the reader's text
     size makes that impossible, and then they stack */
  .part-head { gap: .7rem; flex-wrap: wrap; }
  .part-num { flex: none; }
  .part-head > div:not(.part-num) { flex: 1 1 12rem; min-width: 0; }
  .layer { grid-template-columns: 66px 1fr; gap: .8rem; }
  .verdict { font-size: 1.25rem; padding-left: 1rem; }
  h1 { font-size: clamp(2rem, 9.4vw, 2.8rem); hyphens: none; word-break: normal; overflow-wrap: break-word; }
  h2 { font-size: clamp(1.5rem, 6.4vw, 2rem); }

  /* A machine's configuration is a label column beside a row of choices.
     At 360px the two together are wider than the phone, and because they
     sit in a grid track they drag the whole column — and the tables in it
     — sideways with them. The label goes above its choices instead. */
  /* Three key names in a wrapping row, each of which grows an (i) disc
     once help.js mounts, re-wraps from one line to three under the
     reader. One per line from the first paint instead. */
  .optkeys { display: grid; grid-template-columns: 1fr; gap: .35rem; }

  .opts { grid-template-columns: 1fr; gap: .15rem; }
  .opts .olab { white-space: normal; margin-top: .45rem; }
  .mrow, .mrow-top, .row, .panel, .cf-grid > * { min-width: 0; }

  /* ---- 3.1 instrument controls, stacked in the order a reader needs ----
     Budget first and full width, then the presets, then the switches. */
  .controls, .banks, .ovr.show, .assume, .units, .keyrow, .tiles, .minitab {
    grid-template-columns: 1fr;
  }
  .tiles, .keyrow { grid-template-columns: repeat(auto-fit, minmax(min(9.5rem, 100%), 1fr)); }
  .controls { gap: 1.3rem; }
  .ctl.budget, .ctl:has(input[type="range"]) { order: -2; }
  .presets, .stops { order: -1; }

  /* segmented toggles wrap instead of clipping */
  .seg, .seg2, .switch, .modes {
    display: flex; flex-wrap: wrap; width: 100%;
    border-radius: 6px; overflow: visible;
  }
  .seg button, .seg2 button, .switch button, .modes button {
    flex: 1 1 auto; min-width: max(44px, max-content); border-radius: 4px;
  }
  .seg button + button, .seg2 button + button { border-left: none; }
  .presets { gap: .5rem; }
  .presets button { flex: 0 1 auto; border-radius: 999px; }

  /* The scaling page's budget control is a machined ruler: the readout
     pinned above the track, the four worked budgets ticked along it. On a
     phone the ruler unfolds: the readout is a block above its track, and
     the four named buttons under it, which sit in one row on a desktop,
     stack two to a row so each name has its own line. */
  .ruler { padding: 0; }
  .ruler .readout { position: static; margin-bottom: .55rem; }
  .ruler .exact { align-self: flex-start; margin-top: .35rem; }
  /* Two to a row: the name and the budget it sets will not both fit
     beside another pill on a 320px measure, so the pill carries them
     stacked and the four sit as a tidy 2x2 under the track. */
  .stops {
    display: grid; grid-template-columns: 1fr 1fr; gap: .45rem; margin-top: .85rem;
  }
  .stops button {
    white-space: nowrap;
    border: 1px solid rgba(232,201,115,.45); border-radius: 6px;
    background: rgba(195,154,59,.1); padding: .35rem .5rem;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .1rem; min-height: 48px; min-width: 0;
  }
  .stops button b { display: block; }
  .stops button.on {
    background: var(--inst-accent, #E3C387); color: var(--inst-bg, #0B2116);
    border-color: var(--inst-accent, #E3C387);
  }
  .stops button.on b { color: var(--inst-bg, #0B2116); }

  /* ---- 3.2 bottom sheets ----
     An explanation, a drawer or a detail card on a phone arrives from the
     bottom of the screen instead of inserting itself into the middle of
     the page under the reader's finger.

     Every one of them states its height twice, in `vh` and then in `dvh`.
     `vh` is the viewport a phone has with its URL bar hidden, so a sheet
     anchored to the bottom edge and capped in `vh` can run further down than
     the reader can see and its own scroll stops short of its last row — the
     fault the owner hit on the menu on 2026-09-04. `dvh` is the height he
     actually has; a browser that does not know the unit drops the second
     line and keeps the first. */
  .hpanel:not([hidden]) {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    z-index: 95; margin: 0; max-width: none;
    max-height: min(64vh, 30rem);
    max-height: min(64dvh, 30rem);
    overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    background: var(--card); color: var(--ink-soft);
    border: none; border-top: 3px solid var(--ever);
    border-radius: 16px 16px 0 0;
    padding: 1.5rem 1.2rem calc(1.4rem + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -10px 44px rgba(11,33,22,.4);
    animation: sheetup .24s var(--sheet-ease) backwards;
  }
  .instrument .hpanel:not([hidden]) {
    background: var(--inst-panel, #10281B); color: var(--inst-fg-2, #D6D0BF);
    border-top-color: var(--inst-accent, #E3C387);
  }
  /* the grab handle, so it reads as a sheet you can dismiss */
  .hpanel:not([hidden])::before {
    content: ""; position: absolute; top: .6rem; left: 50%;
    transform: translateX(-50%);
    width: 42px; height: 4px; border-radius: 2px;
    background: var(--line-strong);
  }
  .instrument .hpanel:not([hidden])::before { background: rgba(231,226,211,.32); }
  /* A sheet slides up from just below where it lands, not from a whole
     screen below. Chrome does not start an animation on content that was
     inside a closed <details> when the rule was applied — the assumptions
     drawer opened stranded 591px below the window every time — and a
     keyframe that begins off-screen turns any such stall into a sheet
     nobody can see. Eighteen pixels is enough to read as arriving, and a
     stall costs eighteen pixels. */
  @keyframes sheetup { from { transform: translateY(18px); opacity: .4; } }

  .sheet-scrim {
    position: fixed; inset: 0; z-index: 90;
    background: rgba(11,33,22,.5);
    animation: scrimin .2s ease backwards;
  }
  @keyframes scrimin { from { opacity: 0; } }

  /* The assumptions drawer is the same sheet, without the entrance: Chrome
     does not start an animation on content that was inside a closed
     <details> when the rule was applied, so it would arrive stalled at its
     first frame. Opening the drawer is a visible enough change on its own. */
  details.drawer[open] > .drawerbody, details.sheeted[open] > *:not(summary) {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 95;
    max-height: 70vh;
    max-height: 70dvh;
    overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    background: var(--inst-panel, #10281B);
    border-top: 3px solid var(--inst-accent, #E3C387);
    border-radius: 16px 16px 0 0;
    padding: 1.5rem 1.15rem calc(1.4rem + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -10px 44px rgba(0,0,0,.5);
  }

  /* ---- 3.3 the hero ---- */
  .hero-cover .hero-copy h1 { font-size: clamp(2rem, 9vw, 2.7rem); }
  .hero-cover .kicker { font-size: 14px; letter-spacing: .16em; }
  .hero-meta .hm-row { grid-template-columns: 1fr; gap: .7rem; }

  /* ---- 3.4 motion ---- */
  @media (prefers-reduced-motion: reduce) {
    .hpanel:not([hidden]), .topnav.open, .sheet-scrim,
    details.drawer[open] > .drawerbody { animation: none; }
    * { scroll-behavior: auto; }
  }
}

/* ============================================================
   3.4 THE TOPOLOGY, ON A PHONE
   ------------------------------------------------------------
   The page is a wide drawing beside a control rail. On a phone the
   drawing is already vertical — people, then the network, sign-on, the
   front doors, the routers, the serving room, the way out, the cloud,
   and the rail of things that keep it running — but the control rail
   was stacked under a drawing 1,500px tall, so the controls were four
   screens away from the thing they control, and the drawing landing
   after first paint shoved them off the screen (a 0.93 layout shift,
   measured on a cold load at 390px).

   So on a phone the rail stops being a column and becomes three
   separate things: the title above the drawing, the controls docked to
   the bottom of the screen where a thumb rests, and the node card as a
   sheet that arrives when a node is tapped. Docking the controls takes
   them out of flow, which is also what makes the drawing's arrival
   cost nothing.
   ============================================================ */
@media screen and (max-width: 900px) {
  /* the rail's three children join the stage's own grid */
  .stage > .rail { display: contents; }
  .stage > .canvas { order: 0; }
  .rail-head {
    order: -1; border-bottom: 1px solid var(--inst-line, rgba(231,226,211,.14));
    padding: .9rem 0 .8rem;
    /* The two paragraphs here sit directly above the drawing, so when the
       web font swaps in and one of them re-wraps by a line, the whole
       drawing and the dock below it step down 23px. Metric-matched
       fallbacks make the swap cost a line rather than a paragraph; this
       gives that line somewhere to go. */
    min-height: 19.6rem;
  }
  .canvas { padding-bottom: .5rem; }

  /* the controls, docked where a thumb rests */
  .rail > .console, .stage .console {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
    order: 2; margin: 0;
    background: var(--inst-panel, #10281B);
    border: none; border-top: 1px solid var(--inst-accent, #E3C387);
    box-shadow: 0 -6px 26px rgba(0,0,0,.45);
    padding: .55rem var(--pad) calc(.5rem + env(safe-area-inset-bottom, 0px));
    max-height: 46vh;
    max-height: 46dvh;
    overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  }
  /* the page keeps its last screen clear of the dock */
  body.instrument .map-shell, .stage { padding-bottom: 15rem; }
  .stage { padding-bottom: 0; }
  .topo-dockpad { height: 14rem; }

  .console .readout { margin-bottom: .3rem; }
  .console .minibank { margin-top: .5rem; }
  /* The four money stops print their own figures from script, so the row
     is 0px tall in the first paint and 44px a moment later — and because
     the dock is anchored to the bottom of the screen, everything above
     the row moves up when it arrives. The row's height is reserved, and
     so is the dock's, so the dock is drawn once and stays put. */
  .console .stops { flex-wrap: wrap; min-height: 44px; }
  /* measured cold: 349px at 390 and 368px at 360, once the four stops have
     printed their figures and the fold's control is in place */
  .stage .console { min-height: 23rem; }

  /* The fold is in the CSS as well as the script, so the dock is compact
     in the very first paint rather than settling into it: what belongs
     behind the control is hidden here from the start, and shown again
     once the script has moved it inside. */
  .stage .console > .minibank,
  .stage .console > .minihint { display: none; }
  .dockmore .minibank, .dockmore .minihint { display: block; }

  /* the rest of the dock, folded behind one control */
  .dockmore { margin-top: .45rem; }
  .dockmore > summary {
    display: flex; align-items: center; gap: .5rem;
    min-height: 44px; cursor: pointer; list-style: none;
    font-family: var(--sans); font-size: 14px; font-weight: 700;
    letter-spacing: .04em; color: var(--inst-accent, #E3C387);
    border-top: 1px solid var(--inst-line, rgba(231,226,211,.14));
  }
  .dockmore > summary::-webkit-details-marker { display: none; }
  .dockmore > summary::before {
    content: ""; width: 9px; height: 9px; flex: none;
    border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
    transform: rotate(-45deg); margin-bottom: 3px;
    transition: transform .18s var(--sheet-ease);
  }
  .dockmore[open] > summary::before { transform: rotate(45deg); margin-bottom: 0; }
  .dockmore[open] {
    max-height: 42vh;
    max-height: 42dvh;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
  }
  @media (prefers-reduced-motion: reduce) {
    .dockmore > summary::before { transition: none; }
  }

  /* the node card arrives as a sheet, and only when a node is asked for */
  .card-scroll {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 95;
    max-height: 72vh;
    max-height: 72dvh;
    overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
    background: var(--inst-panel, #10281B);
    border-top: 3px solid var(--inst-accent, #E3C387);
    border-radius: 16px 16px 0 0;
    padding: 1.5rem 1.15rem calc(1.4rem + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -10px 44px rgba(0,0,0,.55);
    transform: translateY(100%);
    transition: transform .26s var(--sheet-ease);
    visibility: hidden;
  }
  body.card-open .card-scroll { transform: none; visibility: visible; }
  body.card-open .console { z-index: 40; }
  .card-scroll::before {
    content: ""; position: absolute; top: .6rem; left: 50%;
    transform: translateX(-50%);
    width: 42px; height: 4px; border-radius: 2px;
    background: rgba(231,226,211,.32);
  }
  .nodecard .proof a { display: flex; align-items: center; min-height: 44px; padding: 0; }

  /* the group captions inside the drawing are scaled with it, so they are
     set in user units large enough to survive the scaling */

  @media (prefers-reduced-motion: reduce) {
    .card-scroll { transition: none; }
  }
}

/* ------------------------------------------------------------
   Reserving the drawing's room before it is drawn
   ------------------------------------------------------------
   Both graphs are drawn by script into an empty div, and both pages
   put their scripts after the markup — so the browser paints the page
   with that div at its minimum height and then, a beat later, a
   drawing 1,500 to 2,800px tall lands in it and shoves everything
   below off the screen. Measured cold at 390px, that was 0.69 of a
   screen of movement on the topology page.

   The room is reserved with an aspect ratio, because the drawing's
   own box is 430 units wide and its height is known: it is the tall
   layout's stacked rows. The height is not a constant — the rows grow
   as the drawing is scaled down, and the number of machines moves with
   the budget — so these are the ratios measured for each page at its
   opening budget, in the three width bands the row height falls into.
   A reservation a little out costs tens of pixels of movement instead
   of thousands, and the reader sees the page settle rather than jump.
   ------------------------------------------------------------ */
@media screen and (max-width: 370px) {
  .stage #graph { aspect-ratio: 430 / 1950; }
  .sc-shell #graph { aspect-ratio: 430 / 2050; }
  .sc-shell .panel.console { min-height: 2600px; }
}
@media screen and (min-width: 370.02px) and (max-width: 500px) {
  .stage #graph { aspect-ratio: 430 / 1850; }
  .sc-shell #graph { aspect-ratio: 430 / 1900; }
  .sc-shell .panel.console { min-height: 2470px; }
}
@media screen and (min-width: 500.02px) and (max-width: 900px) {
  .stage #graph { aspect-ratio: 430 / 1650; }
  .sc-shell #graph { aspect-ratio: 430 / 1580; }
  /* the console's eleven explanations arrive together; its settled height,
     measured cold, is 2,266px at 500 and 1,544px at 768 */
  .sc-shell .panel.console { min-height: 1544px; }
  /* Every note under a configurator control is written from the engine's
     own numbers, so the page paints with them empty and then takes 250px
     of text at once — which pushed the option keys clean off the screen.
     Their settled heights at this width, measured cold. */
  #mirrorNote { min-height: 98px; }
  #audNote { min-height: 50px; }
  #model-note { min-height: 142px; }
  #work-note { min-height: 215px; }
  #gen-note { min-height: 291px; }
}
@media screen and (max-width: 900px) {
  /* once the drawing is in, it sets its own height */
  .stage #graph:has(svg), .sc-shell #graph:has(svg) { aspect-ratio: auto; }
  .stage #graph, .sc-shell #graph { min-height: 0; }
}

/* ============================================================
   3.5 THE CAMPUS MAP
   ------------------------------------------------------------
   The aerial is one photograph with seven college circles on it. At
   390px a circle is about 24px across, so the map is pinched and
   dragged rather than squinted at; mobile.js wires the gestures and
   this gives the stage they move.
   ============================================================ */
.map-stage { transform-origin: 50% 50%; will-change: transform; }
.mapbox { touch-action: pan-y; }
.mapbox.zoomed { touch-action: none; cursor: grab; }
@media screen and (prefers-reduced-motion: no-preference) {
  .map-stage { transition: transform .12s linear; }
}
@media screen and (max-width: 760px), screen and (pointer: coarse) and (max-width: 1024px) {
  /* At the scale a phone draws the map, a name set large enough to read
     would cover the campus — so the circles carry their number and the
     list under them carries the names. Pinching in brings the names back. */
  .blabel, .bsub { display: none; }
  .bkey { display: block; }
  /* the gradient legend under the map is drawn in its own 300-unit box */
  .scalebar text { font-size: 15px; }
  .ckey { display: inline-flex; }
  .mapbox.zoomed .blabel { display: block; font-size: 42px; }
  .mapbox.zoomed .bkey { display: none; }
  .maplegend { grid-template-columns: 1fr; }
  .legend-side { justify-items: start; text-align: left; }
  .modes { width: 100%; }
  /* An implicit grid column is sized `auto`, which is max-content: a row
     whose longest unbreakable run is wider than the panel makes the whole
     column wider than the panel, and the rows hang 2px off the screen.
     `minmax(0, 1fr)` lets the column be as narrow as it is given. */
  .clist { gap: .4rem; grid-template-columns: minmax(0, 1fr); }
  .crow { padding: .7rem .8rem; min-width: 0; }
  .crow h4 { flex-wrap: wrap; }
}

/* ------------------------------------------------------------
   The appendices, on a phone: what was tried and not kept
   ------------------------------------------------------------
   Twenty-eight appendices in one 1.35 MB document, which a phone has to
   lay out in full before it can show the first — most of the four and a
   half seconds it takes to be ready on a 4G connection.

   `content-visibility: auto` with `contain-intrinsic-size` on each
   appendix was measured here and taken out again. It cut being-ready
   from 4,575ms to 4,041ms, and it cost a 0.125 layout shift at 768px,
   because a guessed height for a section is wrong and the page settles
   when the real one arrives. Half a second of a reference document's
   loading time is not worth the page moving under a reader. If it is
   revisited, the guess has to come from the sections' real heights
   rather than one number for all twenty-eight.
   ------------------------------------------------------------ */

/* ============================================================
   4. THE TABLET  (761-1024px): two columns, but touch-sized
   ============================================================ */
@media screen and (min-width: 761px) and (max-width: 1024px) {
  .stats { grid-template-columns: repeat(2, 1fr); }
  .tablewrap, .scrollx { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .seg, .seg2, .switch, .modes { flex-wrap: wrap; }
}

/* The close on any sheet is a touch affordance wherever a sheet appears. */
.sheet-close {
  position: absolute; top: .5rem; right: .5rem;
  min-width: 44px; min-height: 44px; width: 44px; height: 44px;
  display: grid; place-items: center;
  background: transparent; border: none; cursor: pointer;
  font-family: var(--sans); font-size: 16px; font-weight: 700;
  color: var(--ink-faint);
}
.sheet-close span[aria-hidden] { font-size: 16px; }
.instrument .sheet-close { color: var(--inst-dim, #A9B3A2); }

/* A coarse pointer at any width deserves a 44px control. */
@media screen and (pointer: coarse) {
  .topnav a, .menu-btn, .btn { min-height: 44px; display: inline-flex; align-items: center; }
}

/* ============================================================
   5. THE FIVE-LINE SUMMARY on a narrow screen
   ------------------------------------------------------------
   The card's label column is 8.5rem, which holds a 10.9px label
   on a desktop. Under the type floor that same label is 14px and
   no longer fits, so on a phone it goes above its line instead of
   beside it, and on a touch tablet — where there is room — the
   column widens rather than wrapping "What the plan does" onto
   two lines.
   ============================================================ */
@media screen and (max-width: 760px) {
  .five { padding: .85rem .95rem .95rem; margin-bottom: 1.4rem; }
  .five-list > li { grid-template-columns: 1fr; gap: .15rem 0; padding: .62rem 0; }
  .five-k { letter-spacing: .04em !important; }
  .five-v { font-size: 16px; line-height: 1.5; }
  /* the numbers stack rather than run on, so a chip stays with its number */
  .five-n { display: block; }
  .five-n + .five-n { margin-top: .25rem; }
  .five-n + .five-n::before { content: none; }
}
@media screen and (min-width: 761px) and (max-width: 1024px) {
  .five-list > li { grid-template-columns: 10.5rem minmax(0, 1fr); }
}
