/* scenario_results.mobile.css -- the phone layout for the scenario-results
   applet. Loaded after scenario_results.css and overrides it only inside the
   one media query below; the desktop sheet is never edited for phone reasons
   (the seam rule with proj/applet-improvements, PLAN-mobile-explore-mvp §5).

   Layout on a phone: the map owns the screen. The measurements dock is
   permanently on it as a drag-to-resize bottom sheet, at rest showing only its
   drag row -- the grip strip above the one-line readout; the sidebar opens over
   the map as a full-height sheet from the top, raised by a gear in MapLibre's
   top-right control stack. There is no bottom bar any more (Taylor,
   2026-09-12). The legend collapses to a title chip at top-left. Every dock
   panel reflows to one vertical stack and a table wider than the phone
   scrolls inside its own box (ruling D3). The
   mode-choice decomposition becomes one card per mode (treatment B), which
   needs sheets.js to copy the column headers into `td[data-label]`. The
   sidebar sheet is denser than the desktop sidebar it is made of: 36px
   targets, 13px type, one-row scenario and button+select rows (S9).

   The media query MUST stay byte-identical to LAYOUT_MQ in layout.js, so the
   CSS and the JS can never disagree about which layout is on screen. */

/* Phone-only chrome. These rules sit outside the media query on purpose:
   the markup is always present, and on a desktop it must not render. */
#panel-close { display: none; }
#ctrl-settings { display: none; }

@media (max-width: 760px), (max-height: 500px) {
  /* The dock's resting height, and so the height the map gives up at the
     bottom: an 18px grip strip over the 38px readout. It was 84px while a
     bar carried the readout plus a 46px Controls/Results button row; the
     buttons are gone (the gear raises Controls, the dock never closes), so
     the same chrome costs 28px less. No safe-area term: the viewport meta
     has no `viewport-fit=cover`, so the browser keeps the page above the
     home indicator / gesture bar itself and `env(safe-area-inset-*)` reads
     0. With cover, the inset became a ~20-34px empty grey band under the
     buttons on Android Chrome edge-to-edge (Taylor, 2026-09-12). */
  /* The lines' pitch (2026-10-21, Taylor: "remove some of the empty
     vertical space between the summary bar's lines"; tightened again
     2026-10-22, "even less blank space"): line 1 is 30px and each chip line
     28px, where both were 44 (the sentence bar, 2026-10-19, ruling 4), then
     34 / 32; the grip row is 12px, was 18. A finger target stays 44px: every
     chip button is 44px tall and reaches 8px past its line with a negative
     margin (line 1's name: 3px up, 11px down), so neighbouring lines'
     targets overlap by up to 16px and the later one in the DOM (the lower line) takes a press in the overlap. --flow-pad is 4px,
     so the last resting line's target loses its bottom 4px past the screen's
     edge (40 of 44px reachable): a pad of 8 kept it whole but showed the top
     of the next, below-the-fold line as a sliver (Taylor, 2026-10-22). Flip
     keeps a 44px line 2: its A | B segment is a drawn box, not a word with an
     invisible reach. */
  :root {
    --grip-h: 12px; --readout-h: 30px; --line2-h: 0px; --flow-pad: 0px;
    /* Plus the sheet's own 1px top border: --bar-h is the dock's BORDER box,
       and without the term the drag row overflows its resting height by
       exactly that border and the readout's last pixel is clipped away by
       `overflow: hidden`. sheets.js adds the same 1px (as `clientTop`) to the
       drag floor, so a pull to the floor lands on the resting height rather
       than one pixel under it. */
    --bar-h: calc(var(--grip-h) + var(--readout-h) + var(--line2-h) + var(--flow-pad) + 1px);
  }
  /* The chips' first line (line 2, the scenario, and whatever clauses fit
     beside it) adds its row to the resting sheet whenever the study has more
     than one scenario (applet-ui-cleanup, 2026-10-18; the scenario clause is
     that line, first on a phone since 2026-10-22). With one scenario the
     chips start below the fold, as the traveller chip always did. */
  :root:has(#summary-line2:not([hidden])) { --line2-h: 28px; --flow-pad: 4px; }

  /* ---- The frame ---- */
  /* `dvh` tracks mobile Safari's URL bar; `vh` is the fallback for browsers
     without it. Both #app and body lose their flex column: the two sheets and
     the bar are all fixed, so nothing needs to share the height any more. */
  body { display: block; }
  #app { display: block; height: 100vh; height: 100dvh; }
  /* The map runs the whole height and the dock floats on it (Taylor,
     2026-09-12), so the basemap shows through the sheet's rounded top corners
     instead of stopping square above them. --bar-h stops being the map's
     bottom inset and stays what it always was for everything else: the dock's
     resting height, which is where the Controls sheet stops and what the
     MapLibre corner controls clear. map.js pads the opening fitBounds by the
     dock's measured height so the activity peak still lands on the middle of
     the VISIBLE map, not of the container. */
  #map-area { position: absolute; inset: 0; }
  /* MapLibre's bottom corners (the attribution) would otherwise sit under the
     resting dock. They clear it, and do not follow a dragged sheet -- the
     attribution is not worth reflowing on every drag frame. */
  #map .maplibregl-ctrl-bottom-left, #map .maplibregl-ctrl-bottom-right {
    bottom: var(--bar-h);
  }

  /* ---- The readout ---- */
  /* The summary strip: indicator · value on one line. The value never
     shrinks; the indicator takes the rest and ellipsizes (the legend chip
     names it in full). No zone name: ids mean nothing to a reader and a
     tract name ellipsized at 390px carried nothing the map's highlight does
     not. It now sits inside the dock's drag row, under the grip, and so
     rides up and down with the drag (Taylor, 2026-09-12) instead of staying
     on a bar below the sheet. It is still visible under the Controls sheet,
     which stops at --bar-h: pick an indicator in Controls and the readout
     changes under the sheet's foot, as before. It takes the row's full width:
     the ⇆ / ✕ pair that used to share it is gone from the phone layout. */
  #mobile-summary {
    display: flex; align-items: center; gap: 8px; height: var(--readout-h); flex: 0 0 auto;
    padding: 0 12px; font-size: 14px; line-height: 1.2; color: var(--brand-ink);
    white-space: nowrap;
  }
  /* The name is a button (S12) that opens the indicator menu below. It
     looks like the text it was: no chrome, the strip's font and colour, an
     arrow as the one affordance -- a flex sibling of the text rather than
     its tail, so the ellipsis on a long profile-suffixed title cannot
     swallow it. The visible text stays the legend title, which carries the
     profile suffix and units the sidebar labels lack. */
  #ms-ind {
    flex: 1 1 auto; min-width: 0; display: flex; align-items: center;
    height: 44px; margin: -3px 0 -11px; padding: 0 0 8px; border: 0; background: none;
    font: inherit; color: inherit; text-align: left; cursor: pointer;
  }
  /* The 44px reaches only 3px above line 1 (the 8px bottom padding keeps
     the name centred on the line): any higher and it covers the grip pill,
     whose tap pops the sheet to reading height (Taylor, 2026-10-22). Its
     lower 11px lies under line 2's chip targets, which take the overlap. */
  /* The headline: 16px on one line. When the name does not fit at that size
     (a profile suffix -- "People per km² ($68k–$113k | Car | LTS≤1)"),
     summary.js sets `.wrap` on the button and the name alone drops to 13px
     over two clamped lines; the value keeps its size. The strip's 30px is
     what two 15px lines need (line 1 is 30px), so --bar-h never moves. */
  #ms-ind-text {
    flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
    font-size: 16px; font-weight: 700; color: var(--brand-ink);
  }
  #ms-ind.wrap #ms-ind-text {
    font-size: 13px; line-height: 15px; white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    line-clamp: 2;
  }
  /* The picker's one affordance, so it carries the weight of looking like a
     control: 18px and --n-44 rather than 14px and --n-88 (Taylor,
     2026-09-12). `line-height: 1` keeps the taller glyph from growing the
     30px row it sits in. */
  #ms-ind:has(#ms-ind-text:not(:empty))::after {
    content: "\25BE"; flex: none; margin-left: 5px;
    font-size: 18px; line-height: 1; color: var(--brand-ink);
  }
  #ms-ind[aria-expanded="true"]::after { content: "\25B4"; }
  #ms-ind:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; border-radius: 3px; }

  /* ---- The indicator menu ---- */
  /* The menu itself (#ind-menu, .msm-*) is styled in scenario_results.css
     since PLAN-dock-controls S2 -- it is the desktop dock header's picker
     too. What is phone-specific is its geometry: edge to edge, and the
     resting fallback anchor at the old bar height when indicator_picker.js
     has not yet positioned it against the button. */
  #ind-menu, #profile-menu, #param-menu { left: 8px; right: 8px; width: auto; bottom: calc(var(--bar-h) + 6px); }
  /* Pills grow to a finger's height on a phone. */
  #profile-menu .pm-opt, #pm-done { min-height: 36px; padding: 6px 14px; font-size: 14px; }
  /* The summary bar is the sheet here: no box of its own, so the drag row
     (lines 1-2) and #mp-header (below the fold) are the sheet's children as
     before. The desktop's subject word stays off -- the phone reads the pin
     off the map (Taylor, 2026-10-18). */
  #summary-bar { display: contents; }
  #summary-bar .sheet-handle { display: flex; }
  #summary-bar .sheet-grip { display: flex; }
  #sb-subject { display: none; }
  /* The tag form (Taylor, 2026-10-20): no verb words on a phone -- the name
     at the left, "region 436 people/mi²" at the right. Outcome's second
     clause (#ms-delta, ", up 0.4 from existing conditions") is a desktop
     one; the phone writes the change into the value instead (.ms-oc). */
  #ms-pred { display: contents; }
  #ms-lead, #ms-val .ms-conn, #ms-delta, #subject-tip { display: none !important; }
  #ms-val { margin-left: auto; }
  #ms-ind::after { content: none; }
  /* The value, figure and unit (summary.js valueSpans): the figure at 19px
     is what the glance at rest is for; the unit small beside it. */
  #ms-val { flex: 0 0 auto; margin-left: auto; display: flex; align-items: baseline; gap: 4px;
            font-variant-numeric: tabular-nums; }
  #ms-val .ms-fig { font-size: 19px; line-height: 1; font-weight: 700; }
  #ms-val .ms-fig--word { font-size: 14px; font-weight: 600; }
  #ms-val .ms-unit { font-size: 13px; font-weight: 600; color: var(--brand-ink); }
  #ms-val.placeholder { display: flex; }
  /* Line 1 as the compare benefit (2026-10-20): two figures on one line
     ("13k person-h/yr | 5.1 min-eq/person/yr lost") next to the indicator name
     at 390px -- set a step smaller than a single figure so the name keeps
     its two lines rather than ellipsizing to "G… a…". */
  #ms-val.ms-benefit { display: flex; gap: 3px; }  /* the desktop's is `display: contents` */
  #ms-val.ms-benefit .ms-fig { font-size: 16px; }
  #ms-val.ms-benefit .ms-unit { font-size: 11px; }
  #ms-val.ms-benefit .ms-region { font-size: 13px; margin-right: 0; }
  #ms-val.placeholder .ms-fig { font-size: 14px; font-weight: 400; color: var(--text-muted); font-style: italic; }
  /* "Region" before the no-selection figure, the subject word in the
     sentence's connective face (the phone has no #sb-subject): brand slate,
     7.1:1 on the parchment ground (it was --n-88 on plate, 2.76:1). */
  #ms-val .ms-region { display: inline; font-family: var(--font-conn); font-style: italic; font-weight: 400;
                       font-size: 15px; color: var(--brand-slate); font-variant-ligatures: no-common-ligatures;
                       margin-right: 2px; }
  /* Flip (PLAN-each-view S2, D32): "<side name> · Region 44.1%" / "<side
     name> | 44.1% | Walk". The side's name is the Flip control's own word
     for it; it is the one part that gives way -- the value slot may shrink
     (so it is no longer flex: none), and inside it only the name ellipsizes.
     The figure never does. */
  /* Slice 2 (2026-10-19): the side name gives way before the indicator name
     does (flex-shrink 20 against the name's 1) -- the Flip segment on line 2
     names the side too. min-width 0, because an overflow-hidden child's
     min-content is still its whole word: at auto the slot never shrank and
     the indicator name was the one ellipsized. The figure (flex: none)
     keeps its width inside. */
  #ms-val.ms-flip { display: flex; align-items: baseline; flex: 0 20 auto; min-width: 0; }
  /* `display: block` re-shows it: the desktop sheet hides .ms-side (its
     #sb-subject says the side), and until slice 2 (2026-10-19) nothing here
     undid that, so Flip on a phone printed the separator below with no name
     before it -- a lone "|". */
  #ms-val.ms-flip .ms-side {
    display: block; flex: 0 1 auto; min-width: 2.5em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 14px; font-weight: 400; font-style: italic; color: var(--text-muted);
  }
  /* The separator rides on what FOLLOWS the name, so an ellipsized name
     cannot clip it away. */
  #ms-val.ms-flip .ms-side + *::before { content: "|"; margin: 0 5px 0 1px; font-style: normal; color: var(--n-aa); font-weight: 400; }
  /* ... and only between two texts: no side name, no separator. */
  #ms-val.ms-flip .ms-side:empty + *::before { content: none; }
  #ms-val.ms-flip .ms-region, #ms-val.ms-flip .ms-num { flex: none; white-space: nowrap; }
  #ms-val .ms-num { display: flex; align-items: baseline; gap: 4px; }
  /* Outcome (summary.js phoneDelta, 2026-10-21): B's level, then ", up 0.4"
     -- the direction word in the connective face, as "region" is, and the
     change's figure a step below the level's. Ink, not coloured (a level's
     change is not a gain or a loss). One line when it fits beside the name;
     `.ms-oc-stack` puts the change under the level, right-aligned, the
     comma dropped, both inside line 1. The text nodes between the
     words are real spaces, so .ms-oc is a block of inline text, not a flex
     box (a flex box drops whitespace-only text). */
  #ms-val .ms-oc { flex: none; white-space: nowrap; font-size: 15px; color: var(--brand-ink);
                   margin-left: -4px; }  /* the comma against the level, past #ms-val's gap */
  #ms-val .ms-oc-comma { white-space: pre; }
  #ms-val .ms-oc .ms-fig { font-size: 16px; }
  #ms-val .ms-oc .ms-unit { font-size: 12px; }
  #ms-val .ms-oc-word { font-family: var(--font-conn); font-style: italic; font-weight: 400;
                        color: var(--brand-slate); font-variant-ligatures: no-common-ligatures; }
  /* No gap and a 15px change line: the stack is 35px, so it overflows the
     30px line 1 by under 3px each way and stays clear of the grip pill and
     line 2's words (2026-10-22). */
  #ms-val.ms-oc-stack { flex-direction: column; align-items: flex-end; gap: 0; }
  #ms-val.ms-oc-stack .ms-num .ms-fig { font-size: 17px; }
  #ms-val.ms-oc-stack .ms-oc { font-size: 13px; line-height: 15px; height: 15px; margin-left: 0; }
  #ms-val.ms-oc-stack .ms-oc .ms-fig { font-size: 14px; }
  #ms-val.ms-oc-stack .ms-oc-comma { display: none; }
  /* (The "|" between the name and the value went with the sentence bar: the
     gold-underlined name and the 19px figure separate themselves.) */

  /* ---- The gear (Controls) ---- */
  /* The Links page's only since G7 (the analyst map page has no gear on
     either layout; sheets.js builds none there). A MapLibre control group at
     the top of the top-right stack, above the compass (Taylor, 2026-09-12):
     sheets.js prepends it, so the stack reads
     gear | compass | top-down | 2D/3D. Sized and coloured like MapLibre's own
     buttons -- 29px is the library's own control size -- with the pressed
     state the bar's Controls button used to carry. */
  #ctrl-settings { display: block; }
  #ctrl-settings button {
    display: flex; align-items: center; justify-content: center;
    width: 29px; height: 29px; color: var(--brand-ink); cursor: pointer;
  }
  #ctrl-settings button[aria-expanded="true"] {
    background: var(--sel); color: var(--on-accent);
  }

  /* ---- The gear sheet (#panel; the Links page's sidebar) ---- */
  /* Drops from the top, as tall as its content (title · nav · subtitle · ©)
     and never past the dock. `min-width: 0` is load-bearing: below 380px a
     desktop min-width once widened the layout viewport and left the map 0px
     wide (AUDIT-phone-viewports §2). */
  #panel {
    position: fixed; top: 0; left: 0; right: 0; bottom: auto;
    max-height: calc(100% - var(--bar-h));
    width: auto; min-width: 0; max-width: none;
    border-right: none; z-index: 9; overflow-y: auto;
    box-shadow: 0 4px 14px var(--shade-15);
    transform: translateY(-105%); transition: transform .18s ease-out;
  }
  #panel.open { transform: none; }
  #panel-close {
    display: block; position: absolute; top: 6px; right: 8px; z-index: 1;
    border: none; background: none; font-size: 24px; line-height: 1;
    padding: 4px 8px; color: var(--text-muted); cursor: pointer;
  }

  /* The sidebar head's children, stacked: title, nav, subtitle, ©. */
  #panel-title { display: flex; flex-direction: column; align-items: stretch; gap: 8px;
    height: auto; padding: 10px 12px 12px; }
  #app-title { font-size: 13px; font-weight: 600; color: var(--brand-ink); line-height: 18px; padding-right: 32px; }
  #panel-title .applet-nav { border-radius: 6px; }
  #panel-title .applet-nav a { padding: 7px 8px; font-size: 12px; }
  #panel-title .copyright { order: 4; font-size: 11px; color: var(--text-muted); white-space: normal; }

  /* ---- The analyst map page's #panel (G7) ---- */
  /* Not a sheet there: nothing raises it (no gear since G7), and it holds
     only hidden furniture and, on a failed boot, app.js's init-error banner.
     So it is a zero-height strip pinned to the top, above the map, where the
     banner shows. Scoped to the page with the page head; the Links page's
     #panel stays its gear sheet. */
  body:has(#page-head) #panel {
    position: fixed; top: 0; left: 0; right: 0; max-height: none; z-index: 9;
    transform: none; transition: none; box-shadow: none; background: none; overflow: visible;
  }
  /* The init-error banner as a card above the resting sheet (2026-10-21):
     a strip at the top, as on a desktop, lay over the legend chip's page
     head (z 5) and the map controls, Retry on top of the compass. It clears
     the attribution's (i) too. Retry is a 44px finger target. */
  body:has(#page-head) #panel .init-error {
    position: fixed; left: 8px; right: 8px; bottom: calc(var(--bar-h) + 48px);
    border: 1px solid var(--warn-border); border-radius: 8px; box-shadow: 0 2px 10px var(--shade-15);
  }
  body:has(#page-head) #panel .init-error button { min-height: 44px; }

  /* ---- The page head on the legend chip (G7, Taylor 2026-10-20/21) ---- */
  /* The P20 sizes of shots-2026-10-20-g7: lockup 20px, "Connectome"
     14px. The ☰ button's box is 30x24px; its ::before stretches the hit
     area to 44px tall without making the row 44px. The list's rows are 44px
     (finger targets) and 15px. */
  #legend .page-head { margin: 0 0 5px; padding: 0 0 5px; }
  .brand-lockup svg { height: 20px; }
  .brand-rule { height: 14px; }
  .page-head-title { font-size: 17px; }
  .page-head-title.white-label { font-size: 14px; }
  #page-menu-btn { height: 24px; font-size: 14px; }
  #page-menu-btn::before { content: ""; position: absolute; left: -4px; right: -8px; top: -10px; bottom: -10px; }
  #page-menu-list { min-width: 200px; }
  #page-menu-list a { min-height: 44px; padding: 0 16px; font-size: 15px; }
  #page-menu-list a:hover { background: none; }
  #page-menu-list a:active { background: var(--hover); }
  #page-menu-list a.active::after { font-size: 14px; }

  /* The Advanced switch's row (geo-aggregation S1): a finger-height row
     like the page links above it. */
  .page-menu-adv { padding: 10px 16px; font-size: 15px; }
  .page-menu-adv-sub { font-size: 12px; }

  /* One label style for every section head: SCENARIO and TRAVELER PROFILE
     were small caps, Indicators and City Snapshot were 14px bold titles.
     The label colour is pinned to the 4.5:1 bar on the head's own ground, so
     it moves whenever that ground does: --n-6b was the lightest grey clearing
     4.5:1 on the old #f0f0f0 heads, and rung 3's warm band (--ground-band) is
     darker, which took it to 4.47. --n-66 on the band is 4.8 (the desktop's
     small caps fail axe on the same ground and are a separate fix). */
  .sec-label, .traveler-label, .picker-label {
    font-size: 11px; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--text-muted);
  }
  .sec-label { padding: 6px 12px; }
  .section-body { padding: 6px 10px 8px; }

  /* Scenario: `SCENARIO [baseline v]` on one row. The flat-scenario Explore
     grid carries its own "Scenario" header cell -- a repeat of the section
     label -- over an empty axis-name column that pushed the select to 2/3
     width. Header row hidden, empty column collapsed; a multi-axis study
     keeps the axis-name column, which carries real names there. */
  #scenario-picker { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 6px 10px; }
  #scenario-picker .scenario-section-header { flex: 0 0 auto; margin: 0; }
  #scenario-table-container { flex: 1 1 160px; min-width: 0; }
  .scenario-table { margin-top: 0; }
  .scenario-table-explore .st-header { display: none; }
  .scenario-table-explore .st-axis-name:empty { display: none; }
  .scenario-table-explore { grid-template-columns: minmax(0, 1fr); }
  .scenario-table-explore:has(.st-axis-name:not(:empty):not(.st-header)) {
    grid-template-columns: minmax(0, 90px) minmax(0, 1fr);
  }
  /* Compare (applet-2, 2026-09-13): the A / B table cannot share the strip's
     row -- two selects and their headers in the ~180px left beside
     "Scenario | Explore | Compare" rendered as two bare chevrons with the
     REFERENCE / ALTERNATIVE headers printed over each other, and the sheet
     scrolled 24px sideways (measured at 390px). It takes a line of its own
     under the label + strip. The header row stays: Reference / Alternative
     is what tells the reader which select is which. A flat-scenario study's
     empty axis-name column collapses, header cell included, so the two
     selects split the width; a study with named axes keeps a 90px name
     column as explore does. */
  #scenario-picker:has(.scenario-table-compare) #scenario-table-container { flex: 1 1 100%; }
  .scenario-table-compare:not(:has(.st-axis-name:not(:empty))) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .scenario-table-compare:not(:has(.st-axis-name:not(:empty))) .st-axis-name { display: none; }
  .scenario-table-compare:has(.st-axis-name:not(:empty)) {
    grid-template-columns: minmax(0, 90px) minmax(0, 1fr) minmax(0, 1fr);
  }

  /* Traveler profile: the header's 6px bottom margin only earns its place
     when the Specific controls are open under it. */
  #traveler-section { padding: 6px 10px; }
  .traveler-header { margin-bottom: 0; }
  #uc-specific-controls.visible { margin-top: 6px; }
  .uc-toggle-strip { border-radius: 6px; }
  .uc-toggle-btn { font-size: 12px; padding: 0 12px; }
  .traveler-grid { gap: 6px; }

  /* Indicator rows (dev-only since S3): 13px type in a 36px box, 3px gaps. */
  .ind-group { margin-bottom: 6px; }
  .ind-row { margin-bottom: 3px; }
  .ind-btn { font-size: 13px; padding: 5px 9px; margin-bottom: 3px; border-radius: 5px; white-space: normal; line-height: 1.25; }
  .imp-row-label { font-size: 12px; padding: 3px 3px 3px 0; }
  .tip { width: 15px; height: 15px; font-size: 10px; line-height: 15px; }
  /* ---- The dock sheet (#measurements, "Results") ---- */
  /* Permanently on screen since 2026-09-12 (Taylor): there is no bar to open
     it from and no closed state to open out of. It rests at --bar-h, showing
     its drag row and nothing else, and sheet_drag.js writes an inline px
     height for anything taller -- free between that floor and 24px short of
     the top. A tap on the drag row is the shortcut to 35dvh, which is what
     the stylesheet used to open the sheet at: on an 844px phone that gives
     #mp-body ~230px, the lead rows of any panel (the mapped number and its
     decomposition) with most of the map still on show. Anything with a table
     wants dragging up (AUDIT §5).

     `overflow: hidden` is load-bearing at rest: the flex column's header sits
     below a floor that is now the drag row alone, and without it the header
     paints outside the sheet's rounded top over the map. The height
     transition is what makes the tap read as a movement rather than a jump;
     sheet_drag.js sets `transition: none` for the duration of a drag, so the
     height never lags the finger. `clip` rather than `hidden` (with `hidden`
     as the fallback): a hidden box is still a scroll container, and focus
     returning to a chip whose 44px target reaches past the screen's edge
     scrolled the whole resting bar up 4px (2026-10-22). #mp-scroll does the
     sheet's own scrolling, so nothing needs this box to scroll. */
  /* One parchment ground from the drag row down through the fold, the tab
     strip and the body (Taylor, 2026-10-19): no colour break between the
     bar and what it summarises. The drag row was plate and the body
     parchment-raised. The active tab takes the same ground so it still joins
     the body; inactive tabs keep --ground-trim, Plate until 2026-10-30 (variant T1, the conservative
     one -- alternatives in the applet-ui-cleanup notebook). */
  #measurements { background: var(--ground); }
  .mp-tab.active::after { background: linear-gradient(var(--gold) 0 4px, var(--ground) 4px); }
  /* Four tabs on one line at 390px (Taylor, 2026-10-20: "Summary | By
     purpose | By mode | By profile" wrapped each label onto two lines). The
     words stay whole; the tracking, the side padding and the strip's inset
     give way instead (the caps' 14px slopes still clear the text). */
  #mp-tabs { padding: 6px 6px 0 10px; }
  .mp-tab { white-space: nowrap; padding: 6px 17px 5px; margin-right: -14px; letter-spacing: .05em; }
  /* The access By profile tree scrolls sideways in its column (.stp-col-tbl)
     at 390px; its first column -- the radio and the name -- stays pinned
     while the figures scroll under it (Taylor, 2026-10-20). Opaque ground
     behind the pinned cells, at a lower specificity than the tint / checked /
     hover rows so those still colour them; the radio cell is 18px wide. */
  #mp-body .access-tbl td:nth-child(-n+2),
  #mp-body .access-tbl thead tr:first-child th:nth-child(-n+2) {
    position: sticky; left: 0; z-index: 1; background: var(--ground);
  }
  #mp-body .access-tbl td:nth-child(2),
  #mp-body .access-tbl thead tr:first-child th:nth-child(2) { left: 18px; box-shadow: 1px 0 0 var(--n-dd); }
  #mp-body .access-tbl thead tr:first-child th:nth-child(-n+2) { z-index: 3; background: var(--ground-trim); }
  #measurements {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    flex: none; height: var(--bar-h); z-index: 8; overflow: hidden; overflow: clip;
    border-top: 1px solid var(--n-dd); border-radius: 14px 14px 0 0;
    box-shadow: 0 -4px 18px var(--shade-16);
    transition: height .18s ease-out;
  }
  /* The drag row is the sheet's whole resting state: a 12px grip strip over
     the 38px readout, one 56px surface. Merged rather than stacked as two
     rows of chrome (Taylor, 2026-09-12) -- it buys back 28px of map against
     the bar it replaces. The whole row drags and the whole row taps, with no
     holes: sheet_drag.js is passed `grabsControls`, so a press on the
     indicator picker starts a drag like a press anywhere else on the
     parchment, while the picker keeps its own click for a press that does not
     move (Taylor, 2026-09-12). `touch-action: none` keeps the browser from
     turning the gesture into a page scroll or pull-to-refresh before the
     pointer events see it, and `user-select: none` keeps a slow drag from
     selecting the readout's text -- the drag cannot preventDefault a press on
     the picker without also eating the picker's click.

     The 24px invisible hitbox that used to reach past the row is gone
     (2026-09-12). It existed to make a 44px handle easier to grab; the row is
     56px now and sits on the screen's bottom edge, and since a tap on it
     collapses the sheet, an invisible tap zone lying over the header and the
     first rows of the results would dismiss them from a tap that looks like
     it landed on content. */
  /* Its height is its content's since the chips' flow moved in (2026-10-21):
     the resting sheet (--bar-h) shows the grip, line 1 and the flow's first
     line, and the rest of the flow is revealed on drag-up. sheets.js takes
     the drag floor from --bar-h, not from this box. */
  .sheet-handle {
    display: flex; flex-direction: column; padding: 0;
    height: auto;
    box-sizing: border-box; cursor: grab; flex: 0 0 auto; touch-action: none;
    -webkit-user-select: none; user-select: none;
    border-radius: 14px 14px 0 0; background: var(--ground); position: relative;
  }
  .sheet-grip {
    flex: 0 0 auto; height: var(--grip-h);
    display: flex; align-items: center; justify-content: center;
  }
  /* The grip pill. The drag row was plate against a parchment-raised body
     (2026-09-12) until the sentence bar made the whole sheet one parchment
     ground (2026-10-19); the pill marks the drag surface on its own now. The
     warm family has no mid-tone that is not gold, so it takes --gold-deep
     (2.4:1 on parchment), the one place gold appears without meaning
     "selected". */
  .sheet-grip::before { content: ""; width: 38px; height: 4px; border-radius: 2px; background: var(--gold-deep); }
  /* The chips' flow (2026-10-21, Taylor): line 2 and the chips below the
     fold in one wrapping row -- "under existing conditions ▾ for all
     travellers ▾" on one line where it fits, "by car ▾ on trips to shop ▾"
     after it -- breaking only between clauses: line 2 (connective, chip,
     view) is one flex item, and so is each chip with its connective.
     #mp-header and #mp-params are `display: contents`, so their chips are
     the flow's own items. The access rows' desktop reorder (traveller chip
     first, data-sb-order) is the sentence's, and the phone has no verb for
     it to follow, so the flow keeps DOM order. With no chips the flow is
     empty and the body starts right under line 1. */
  #sb-flow {
    display: flex; flex-wrap: wrap; align-items: center; gap: 0 var(--sb-clause-gap);
    padding: 0 12px var(--flow-pad);
  }
  #mp-header { display: contents; }
  #mp-params:not([hidden]) { display: contents; }
  #mp-header:has(#mp-profile[hidden]):has(#mp-params[hidden]),
  #mp-header:has(#mp-profile.mp-profile--row-off):has(#mp-params[hidden]) { padding: 0; border-bottom: none; }
  /* The profile chip (S4-c, header since 2026-09-20): a clause of the chips'
     flow, on line 2's line where it fits. */
  #mp-profile {
    flex: 0 1 auto; min-width: 0; padding: 0; border-right: 0; height: 28px;
    align-items: center; font-size: 15px; gap: var(--sb-conn-gap);
  }
  #mp-params .mp-param.param-chip { height: 28px; }
  #mp-profile, #mp-params > .mp-param { z-index: 1; }   /* flex items: z-index needs no position */
  #profile-chip-btn, .param-chip-btn { flex: 0 1 auto; height: 44px; margin: -8px 0; align-items: center; }
  #profile-chip-text { flex: 0 1 auto; }
  .profile-chip-caret { font-size: 11px; }
  #profile-chip-bar { right: 0; bottom: 8px; }
  /* The sheet is sized by the drag, not from the inside: the body takes what
     is left under the chrome and scrolls. No resize grip -- the drag row is
     the phone's. */
  /* One scroller, #mp-scroll, holds the body; the body and placeholder
     size to their content inside it. The placeholder still grows to fill,
     which is what keeps its prompt centred. */
  #mp-scroll { flex: 1 1 auto; max-height: none; overflow-x: hidden; overflow-y: auto; }
  #mp-body { flex: 0 0 auto; height: auto; max-height: none; overflow-y: visible; }
  #mp-placeholder { flex: 1 1 auto; height: auto; max-height: none; }
  #mp-resize { display: none; }
  /* The filters: clauses of the chips' flow, wrapping between clauses. */
  #mp-params { gap: 6px var(--sb-clause-gap); }
  .mp-param, .mp-param-label { font-size: 13px; }
  .mp-param select { font-size: 13px; padding: 4px 6px; max-width: 60vw; }
  /* The selection chips: connective + value, at the traveller chip's 15px
     (the connectives are back since the sentence bar; session 6 had dropped
     the labels on a phone); the text may take most of the line. */
  #mp-params .mp-param.param-chip { align-items: center; font-size: 15px; }
  .param-chip-text { max-width: 70vw; }
  .param-chip-caret { font-size: 11px; }

  /* ---- The legend ---- */
  /* Top-left keeps it clear of the bottom sheets and of MapLibre's controls
     at top-right. The same stack as the desktop's since the legend
     restructure (geo-aggregation S1, Taylor 2026-10-28): the page head (G7),
     the ramp full width with its end values under it at 14px (sketch round
     6, reversing the 2026-09-12 values-beside-the-bar ruling), the label,
     the ring key while a ring is drawn, and the row of folds (Units ·
     Geography · Display), all closed at load. The 3D toggle is relayed
     through the #ctrl-3d map control below; the compare-view caption shows
     in Flip and for a fold notice only (main sheet). The width is fixed
     (G7, as on a desktop): the chip used to size to its title, and the page
     menu at its right edge jumped when a fold opened. */
  #legend {
    top: 10px; left: 10px; right: auto; bottom: auto;
    width: min(62vw, 240px); padding: 6px 8px;
    /* The desktop's max-height follows the dock (--sheet-h, G2); the chip
       sits under the sheets here, so it keeps the old 70vh cap. */
    max-height: 70vh;
    /* Below the sheets (8, 9): a fully raised dock's handle sits at 24px from
       the top and the desktop's z-index 900 would put the chip on top of it.
       Still above MapLibre's controls (z 2). */
    z-index: 5;
  }
  /* While the page menu is open the chip comes above the sheets: the list
     and its backdrop are stacked inside the chip's z 5 (page_menu.js), so
     under a raised sheet the list opened behind it (2026-10-21). The sheet
     stops under the head row (sheets.js topInset), so the button itself is
     always reachable. */
  #legend:has(#page-menu-list:not([hidden])) { z-index: 10; }
  #legend .leg-bar { height: 12px; margin: 0 0 2px; }
  /* An end label never breaks inside itself; the two share the row under
     the bar, the centre value (L4) on its own line below them. */
  #legend #leg-min, #legend #leg-max { white-space: nowrap; font-size: 14px; color: var(--brand-ink); }
  #legend #leg-mid { font-size: 13px; }
  #legend .leg-title { font-size: 13px; line-height: 17px; margin: 5px 0 0; }
  #legend #leg-nochange { font-size: 13px; }
  #legend #leg-ringkey { font-size: 13px; }
  /* Three headings (Units, Geography, Display) fit the 240px legend
     edge to edge: spread, not a fixed gap (geo-aggregation S2). */
  #legend .leg-folds { gap: 8px; justify-content: space-between; margin: 7px 0 0; }
  #legend .leg-section-title { font-size: 14px; line-height: 20px; }
  #legend .leg-section-title .collapse-arrow { font-size: 12px; color: var(--text-muted); margin-left: 3px; }
  #legend .leg-freeze { font-size: 14px; }
  .imagery-tile { width: 72px; height: 54px; }
  #legend #leg-compare-view { margin: 3px 0 0; }   /* shown in Flip / for a fold notice only (main sheet) */
  /* The Units fold's strips: 28px tall (36 elsewhere on a phone; sketch
     round 7), above WCAG 2.2 AA's 24px target minimum. */
  #legend #leg-units-body .norm-btn { min-height: 28px; line-height: 1.2; font-size: 12px; }
  #legend #leg-units-body .mp-param-label { font-size: 13px; }

  /* ---- Line 2: the scenario (scenario_picker.js) ---- */
  /* The chips' flow's FIRST line on a phone, alone on it, so the scenario is
     what the resting sheet shows and the parameters start below the fold
     (Taylor, 2026-10-22: the sentence's end is desktop's reason, and a phone
     has no sentence to end). A CSS order, not a DOM move: desktop keeps it
     last. A comparison's two chips wrap between them when they do not share a
     line; each chip takes a 44px finger height inside the 28px row (negative
     margins). Because the clause now sits ABOVE chips that come before it in
     the DOM, the parameters' chips are lifted (z-index) so the lower line
     still takes a press in the overlap, as the DOM order gave it before. */
  #summary-line2 {
    order: -1; flex: 1 0 100%;
    min-height: var(--line2-h); max-width: 100%; flex-wrap: wrap;
    gap: 0 var(--sb-conn-gap); align-items: center;
    padding: 0; border-left: 0; font-size: 14px;
  }
  .scn-chip { flex: 0 1 auto; max-width: 100%; align-items: center; align-self: stretch; }
  .scn-chip-btn { height: 44px; margin: -8px 0; align-items: center; }
  .scn-chip-text, .scn-shared-name { font-size: 14px; }
  #sb-scn-shared { align-items: center; }
  #compare-add { height: 28px; line-height: 26px; }
  #flip-frame { bottom: var(--sheet-h, var(--bar-h)); }
  /* Edge to edge like the other two menus, capped at 60 dvh with the footer
     sticky inside it; anchorMenu opens it upward from the chip on line 2. */
  #scenario-menu { left: 8px; right: 8px; width: auto; bottom: auto; max-height: 60dvh; }
  /* Narrower radio columns than the desktop's 96px: the menu is edge-to-edge
     here and the option labels (a flat study prints whole scenario labels in
     them) need the width more than the circles do. The finger height is on the
     CELL, not the mark -- the circle stays 18px in both layouts. */
  #scenario-menu { --sm-cell: 76px; }
  /* A looser label cap than the desktop's 190px, because the phone's rows are
     14px, not 13, and its menu is wider than 420 on most handsets. The cap
     only ever takes effect in EXPLORE, where one radio column leaves slack: at
     190 the phone wrapped "Region-wide population +50%" onto two lines while
     94px sat empty to its right. In COMPARE the two 76px columns eat the slack
     first, so the label track lands on the leftover (~166px on a 390px
     handset) exactly as the old 1fr did. */
  #scenario-menu { --sm-lab: 230px; }
  /* Smaller still than the desktop's 10px: ALTERNATIVE has to fit a 76px
     column. */
  #scenario-menu .sm-radio { min-height: 36px; }
  #scenario-menu .sm-lab { font-size: 14px; min-height: 36px; }
  /* Finger-height buttons, as #profile-menu's rule above does. */
  #scenario-menu .sm-seg, #sm-cancel, #sm-apply {
    min-height: 36px; padding: 6px 14px; font-size: 14px;
  }

  /* ---- The 2D/3D map control ---- */
  /* sheets.js appends it to MapLibre's top-right corner under the compass
     and top-down buttons, styled as one more control group; the text names
     the view a tap gives, and that label is the whole state display -- no
     active colour (Taylor, 2026-09-12). Hidden off the phone layout, where
     the legend's own toggle is the control. */

  /* ---- The dock panels: the reflow (ruling D3) ---- */
  /* Every panel becomes one vertical stack. The three flex rows and the
     multicolumn body are what laid the desktop dock out sideways; the
     `max-width: none`s release the columns that were sized for a 1280px
     dock. A table wider than the phone scrolls inside its own box. */
  #mp-body { columns: auto; column-gap: normal; column-rule: none; }
  .pn-mc-main, .dcp-cols, .stp-cols { flex-direction: column; align-items: stretch; gap: 10px; }
  .dcp-col-mid, .stp-col-lead, .pn-mc-side { max-width: none; flex-basis: auto; }
  /* Mode choice's Compare O→D lead (polish (k)): the desktop rule sizes it to
     content; stacked, it takes the phone's width and its long Δ words wrap. */
  .stp-col-lead.stp-col-lead--fit { max-width: none; }
  #mp-body .pn-msplit-cmp td.pn-dc-val:last-child { white-space: normal; }
  /* The dock is ONE scroller on a phone: #mp-body (since R17 its wrapper
     #mp-scroll, which adds the parameter strip above it). Side by side these regions
     divide a 1280px dock's width, which is why each is a flex item that may
     shrink and carries its own scroller; stacked they would divide the dock's
     ~220px of HEIGHT instead, and the region at the bottom of the stack gets
     whatever the ones above it leave. That is how the multi-profile
     mode-choice panel lost its per-profile table: 137px of table in a 33px
     slot, inside its own scroller, under a lead block that had already taken
     177px -- and #mp-body had nothing to scroll, because .stp-cols had shrunk
     to fit (Taylor, 2026-09-12). So on a phone every region sizes to its
     content and nothing shrinks; the overflow lands on #mp-body, which is the
     scroller the reader already has a gesture for. Dragging the sheet taller
     still reveals more of the same column, it is no longer the only way to
     reach the bottom of it. */
  .pn-mc-main, .dcp-cols, .stp-cols,
  .pn-mc-tbl, .pn-mc-side, .dcp-col-mid, .stp-col-lead { flex: 0 0 auto; }
  /* .dcp-col-cats is the panel that actually loses data on a desktop dock at
     phone width -- it collapsed to 4px and was clipped with no scrollbar
     (AUDIT §5). Stacked and released it gets the full width back. */
  .dcp-col-cats { flex: 0 0 auto; }
  /* Horizontal only: a table wider than the phone still scrolls inside its own
     box. `overflow-y: visible` alongside it computes to `auto`, which is inert
     here -- the box is content-height, so there is never anything to scroll. */
  .stp-col-tbl, .dcp-col-cats { flex: 0 0 auto; overflow-x: auto; overflow-y: visible; max-width: 100%; }
  .dcp-col-cats table { min-width: max-content; }
  /* Two-up category sub-tables stack when the pair does not fit. */
  .dcp-cat-cols { flex-wrap: wrap; }
  /* The cumulative panel's purpose + by-mode tables are a flex row on the
     desktop; at 390px the second table sat entirely off-screen to the right
     of the scroll box, with nothing to say it was there (found reading the
     panels, 2026-09-11). Stacked, each scrolls on its own. */
  #mp-body .cum-tbls { flex-direction: column; align-items: stretch; overflow-x: auto; }
  #mp-body .cum-tbls > table { max-width: 100%; }
  #mp-body .cum-tbls > * { overflow-x: auto; }
  /* ...but a <table> does not scroll (overflow applies to block boxes), so a
     table wider than the dock was CLIPPED, not scrolled: Mode share's by-mode
     table at 383-388 px in a 362 px dock lost its Δ column (2026-10-22). The
     box above scrolls instead, and the cells pack tighter so the seven-column
     compare tables fit at 390 px (362/362 measured on Burlington, Mode share
     and Vehicle-miles, five zones). */
  #mp-body .cum-tbls .pn-mode-table :is(td, th) { padding-left: 2px; padding-right: 2px; }
  #mp-body .cum-tbls .pn-mode-table th .side-tag { padding: 0 3px; }

  /* ---- Mode choice: one card per mode (treatment B, ruled 2026-09-11) ---- */
  /* The column headers become in-row labels via `td::before { content:
     attr(data-label) }`; sheets.js copies each `thead th` into the cells after
     every render. If the labeler has not run (JS off, or a render it did not
     see) the cells simply show without labels -- the numbers are still right,
     and the formula line under the table still names the columns in order. */
  .pn-mc-tbl { flex: 0 0 auto; max-width: 100%; overflow-x: visible; }
  .pn-decomp, .pn-decomp tbody { display: block; width: 100%; }
  .pn-decomp thead { display: none; }
  .pn-decomp tbody tr {
    display: block; width: 100%;
    margin: 0 0 8px; padding: 6px 8px; border-radius: 6px;
    border: 1px solid var(--shade-12);
  }
  /* Label left, value(s) packed right: a cell may hold more than one node
     ("$0.597" + its "(1.7)" span), and `space-between` would spread them. */
  #mp-body .pn-decomp td {
    display: flex; justify-content: flex-end; align-items: baseline;
    flex-wrap: wrap; gap: 4px 8px; padding: 2px 0; border: 0;
    text-align: right; min-width: 0; white-space: normal;
  }
  #mp-body .pn-decomp td::before {
    content: attr(data-label); text-align: left; color: var(--text-muted);
    font-weight: 400; font-size: 11px; flex: 0 0 auto; margin-right: auto;
  }
  /* A side's label is the side's tag, as its column head is on a desktop
     (util.js sideTag; .side-tag in scenario_results.css). */
  #mp-body .pn-decomp td[data-label-side]::before {
    border-radius: 3px; padding: 0 4px; font-weight: 600;
  }
  #mp-body .pn-decomp td[data-label-side="A"]::before { background: var(--side-a); color: var(--side-a-on); }
  #mp-body .pn-decomp td[data-label-side="B"]::before {
    background: var(--side-b); color: var(--side-b-on); box-shadow: inset 0 0 0 1px var(--side-b-edge);
  }
  /* The mode-specific cell holds N chips; they wrap instead of widening the
     card (Walk's three chips overflowed 390px by 27px in the S2 mock). */
  #mp-body .pn-decomp td.pn-decomp-chips { max-width: none; row-gap: 4px; }
  /* The mode name is the card's heading, not a labelled row. */
  #mp-body .pn-decomp td:first-child {
    display: block; text-align: left; font-weight: 600;
    font-size: 14px; margin-bottom: 4px;
  }
  #mp-body .pn-decomp td:first-child::before { content: none; }
  /* The mapped-mode marks were drawn as cell-edge box-shadows on a row of
     cells; on a card they become the card's own border. */
  #mp-body .pn-decomp tr.pn-mapped-mode { border-color: var(--n-44); border-width: 2px; }
  #mp-body .pn-decomp tr.pn-mapped-mode td,
  #mp-body .pn-decomp tr.pn-mapped-mode td:first-child { box-shadow: none; }
  /* The collapsed unavailable-modes row is a sentence with colspan; it keeps
     its own styling and gets no label. */
  #mp-body .pn-decomp tr.pn-mode-unavail { border: none; padding: 0 2px; }
  #mp-body .pn-decomp tr.pn-mode-unavail td:first-child {
    font-size: 10px; font-weight: 400; margin: 0;
  }
  #mp-body .pn-decomp tr.pn-mode-unavail td::before { content: none; }

  /* ---- Type and touch targets ---- */
  /* 36px is the floor for a finger (S9 ruling; was 40 -- well above WCAG
     2.5.8's 24px, under Apple's 44pt guideline). 42 targets were under it
     and every <select> was at 12px (AUDIT §3). `#app` is for specificity:
     the desktop sheet sizes selects by class and id.
     Selects are 13px at rest and 16px on focus: iOS Safari zooms the page
     when a focused control's type is under 16px, and it decides at focus
     time -- the usual workaround, NOT yet verified on a real phone (S8;
     fall back to 16px at rest if it zooms). */
  #app select, #app input, #app textarea { font-size: 13px; }
  #app select:focus, #app input:focus, #app textarea:focus { font-size: 16px; }
  #app select { padding: 4px 6px; line-height: 22px; min-height: 32px; }
  .ind-btn, .mp-btn, .uc-toggle-btn, .mode-btn, .norm-btn, #btn-3d-toggle { min-height: 36px; }
  /* In the summary bar a toggle is a sentence control: 44px (ruling 4). */
  #summary-bar .norm-btn { min-height: 44px; }
  .uc-toggle-strip { flex-wrap: wrap; }
  input[type="range"] { height: 28px; }
}
