/* mobile.css — THE MONOHULL (Sum, 2026-08-23): on a phone there is no side-by-side. one vertical hull:
   scroll down for the bot, up for the card desk, up again for the other wing. full-width everything,
   bigger text ("i keep forgetting how small text is"). LAYOUT ONLY, media-gated — desktop untouched.
   loads LAST of the structure css (after shell/desk.css) so it wins ties. tag to code: this file. */
/* 3 Sep 2026 — the hull's gate is the PHONE PREDICATE now (zoom.js OZ_PHONE_MQ, same string): width under 800, OR a coarse
   pointer with a short side under 500 — so a phone turned sideways stays in the hull instead of falling onto the desktop layout. */
@media (max-width: 800px), ((hover: none) and (pointer: coarse) and (max-height: 500px)) {
  html { font-size: 17px; }
  body { font-size: 1.06em; }

  /* the hull — the desk grid becomes one column; zones stack and the PAGE scrolls through them */
  #desk { display: flex; flex-direction: column; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  #desk > * { width: 100%; min-height: 0; flex: none; }
  #desk > .zone { flex-direction: column; }
  /* ⚠ the CARD STACK (the middle grid cell) has NO .zone class — in the hull it must size to its content
     or it collapses to 0 and the cards paint into the void (found on glass, 23 Aug) */
  #desk > :not(.zone) { height: auto !important; overflow: visible; order: 0; }
  /* CORRECTION 2026-08-29 late (Sum: "keep top and bottom wings, bots can live there instead —
     launch top where we had launch left, launch bottom where we had launch right") — the hull's
     wings REMAP instead of trailing: the LEFT wing renders as the TOP band (right under the nav,
     where a left-docked bot now lands) and the RIGHT wing as the BOTTOM band. The old order
     (left 2 · right 3 — both below the desk) is superseded; top/bottom zones keep their posts.
     Worst case he accepted: top wing + center cards + bottom wing, three stacked regions. */
  .zone[data-edge="top"]    { order: -2; }
  .zone[data-edge="left"]   { order: -1; }  /* the top wing — was order 2 */
  .zone[data-edge="right"]  { order: 4; }   /* the bottom wing — was order 3 */
  .zone[data-edge="bottom"] { order: 5; }   /* was 4 — stays the floor */

  /* full width, no floors — the 280px open-bar floor and 340px wing width are desktop law only */
  .zone[data-edge="left"] .agent-bar:not(.collapsed), .zone[data-edge="right"] .agent-bar:not(.collapsed),
  .zone[data-edge="left"] .card, .zone[data-edge="right"] .card { width: 100%; min-width: 0; }
  .agent-bar:not(.collapsed) { max-height: 88vh; max-height: 88dvh; }   /* a bot is a full screen you scroll to, not a sliver (dvh 2 Sep: the VISIBLE screen) */
  .zone[data-edge="left"] .card.collapsed, .zone[data-edge="right"] .card.collapsed,
  .agent-bar.collapsed { width: 100% !important; min-width: 0 !important; height: 46px; }   /* a collapsed rail turns strip in the hull */

  .card { margin: 4px 2px; border-radius: 8px; }
  /* 📱 THE PANES (Sum, 3 Sep 2026: "on phone I don't think we need sliders at all, just full size cards. I don't even think we
     need wings if I can have a bot and a card open at the same time… yes, do it"). Under 800px, saved dock geometry — edge,
     size, collapsed-as-rail — is DESKTOP state and is not read: every open bar and card is one visible screen tall, full
     width, in the hull's column; a collapsed one is the 46px strip. The top bar is the switcher: a tile brings its pane onto
     the glass (workspace.js OZ_HULL.scrollTo — the one helper every opener calls). Bot + card open at once = two screens
     stacked; scroll between them; scroll down and the chrome tucks away (the judge), so the pane owns the glass — the arcade
     idiom for everything. Desktop untouched: its geometry survives the trip and comes back on a wide window.
     ⚠ !important here beats the INLINE heights the desk writes for its own geometry — that is the point, not a broken cascade. */
  #desk .agent-bar:not(.collapsed) { height: 100dvh !important; max-height: none; }
  #desk .card:not(.collapsed) { height: 100dvh !important; max-height: none; }
  #desk .card.collapsed { height: 46px !important; }
  /* 3 Sep 2026 — NO SLIDERS ON THE PHONE (Sum: "on phone I don't think we need sliders at all, just full size cards"; the
     toto handle had landed between the bot's controls and the arcade). Every bar and card is full width in the hull, so a
     resize handle has nothing to resize; it goes. Desktop keeps its sliders. */
  .ab-resize, .card-resize { display: none; }

  /* 💬 THE ROOM in the hull (2026-08-29, the phone pass): two flex rows refused to shrink and
     forced the chat screen to 556px inside a 371px card — Vogel's lines clipped mid-word. The
     room-tab strip and the 1997 format bar (font · swatch · B/I) WRAP instead; the screen then
     takes the card's width and pre-wrap does the rest. aiol.css untouched — desktop never clips. */
  .card-aiol .aiol-tabs, .card-aiol .aiol-bar { flex-wrap: wrap; }
  .card-aiol .aiol-screen { max-width: 100%; }

  /* a crowded card-head (the room seats seven controls) is wider than any phone — it SCROLLS
     sideways rather than clipping, so every control stays reachable by thumb. hull-only. */
  .card .card-head { overflow-x: auto; }

  /* 📲 CHROME AWAY lived here for an hour, phone-gated — PROMOTED the same night (Sum, on his
     desktop: "think I want that feature even in landscape") to shell/topbar.css, ungated, every
     size. The desk is flex:1, so it GROWS into the freed rows by construction — his vote: "desk
     grows, wings stay fixed" (wing widths are, and their sliders still adjust them). */

  /* 📱 THE BARS SCROLL, THEY DO NOT WRAP (Sum, 2 Sep 2026, on his iPhone 17: "the top bar is wrapping — I like how the
     bars scroll instead, works well on phone"). topbar.css's wrap (L69, 29 Aug) is desktop law and stays; in the hull a
     wrapped nav is three rows of thumb-sized tiles over a 393px desk. One row that scrolls sideways is the phone idiom —
     the same move .card-head made on 29 Aug. Children hold their size (a tile that shrinks is a tile you cannot hit). */
  #topbar, #subbar, #greenroom-bar { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; max-width: 100vw; }
  #topbar::-webkit-scrollbar, #subbar::-webkit-scrollbar, #greenroom-bar::-webkit-scrollbar { display: none; }
  #topbar > *, #subbar > *, #greenroom-bar > * { flex: 0 0 auto; }
  #topbar { min-height: var(--topbar-h); }

  /* 📐 THE FLOOR IS THE GLASS (Sum, 2 Sep 2026, iPhone 17: "desktop is too wide, right side is bleeding off my screen").
     base.css gives #zoom-root a 360px min-width — the desktop WINDOW floor, so a shrunken window never hides the stop
     button. On a phone with eat-me at 1.25 the stage lays out 100/1.25vw = 314px, the floor lifts it back to 360, the
     transform scales that to 450px, and the right 57px hang off a 393px screen. A phone has no window to shrink; its
     floor is the glass itself, so the 100/z sizing lands the stage exactly on it at every zoom. (zoom.js's overflow
     clamp is gated the same way.) */
  #zoom-root { min-width: 0; min-height: 0; }

  /* 🕹️ THE ARCADE IN PORTRAIT — third cut, 3 Sep 2026 09:30. The first two are UNDONE (Sum: "I can not scroll or press a
     button in butcher block maker… bring scroll back, undo 2 changes back… I want the normal desktop view when I open
     the card, then if I try and scroll have just the game play window fill the screen; shrinks, buttons come back up").
     Cut 1 pinned the card under a thin bar (2 Sep); cut 2 put it in flow one screen tall (3 Sep 09:10). Both made the
     frame the whole glass, and a touch inside an iframe never reaches the desk: Butcher's page is wider and taller than
     the frame, so the swipe scrolled the GAME's document and the desk never moved. Dead end, not a wall.
     Now: the card opens looking exactly as it does on the desk. The game fills the glass only while the chrome is AWAY
     — the same state a scroll-down already produces — and returns into its card the moment the chrome returns. The
     shim inside every game (cabinet.js) relays the finger to the desk's gesture door, so a swipe INSIDE the game is the
     swipe that fills it, and the swipe down that shrinks it back. games.js still stamps html[data-arcade="play"]. */
  /* CORRECTION 3 Sep 09:50 — the fill state is gone too, with the finger relay that fed it (Sum: "drag down is fast drop…
     scroll on piece needs to still fast drop and keep game play big"): a touch inside a game belongs to the game, full stop.
     The arcade card is now a full-screen PANE like every other (below), so the frame is the screen minus its play bar,
     and the game's own portrait pass (score strip as an overlay, full-width board, touch controls) is the game's — riffed,
     not built here. games.js still stamps html[data-arcade="play"] for whatever that pass needs. */

  /* 🕹️ PLAY (Sum, 3 Sep 2026: "in game switch nav to game, on pause back to normal nav… pause button toggles nav, done").
     games.js stamps html[data-game="play"] while a game RUNS (not while paused). Then: the nav folds away, the games card
     loses its head and play bar, and the frame is pinned to the whole visible glass, full width, full height — the ⏸ floats
     top-left (Butcher's own ⏸ sits top-right). Pause removes the attribute and everything here stops applying: the pane,
     the nav, ‹ arcade · ▶ resume, the curtain. No gesture enters or leaves play; the game gets every touch. */
  html[data-game="play"] #topbar, html[data-game="play"] #subbar, html[data-game="play"] #greenroom-bar { display: none; }
  html[data-game="play"] .card[data-card="games"] .card-head, html[data-game="play"] .card[data-card="games"] .arc-pbar { display: none; }
  /* 3 Sep 10:45 — THE BOTTOM GUTTER (Sum: "just need more padding at bottom so it doesn't clip, may mean game play window is
     shorter, to keep full width"). Chrome on iOS draws its bottom bar OVER the last ~95px of the viewport — innerHeight,
     dvh and visualViewport all count that strip as visible, so no padding inside the game can know about it. On a phone the
     play stage stops 96px above the layout bottom; the board is shorter and stays full width. Installed as a PWA there is no
     bar, so the gutter is only the home indicator. Safari counts its bar out of the viewport already, so there the gutter
     is 96px of dark — the price of never clipping, and his call. */
  /* CORRECTION 10:50 — Sum, one screenshot later: "something else changed, the something else is the fix." It was `height:
     auto` with top+bottom: on WebKit the frame's `height: 100%` stopped resolving against it, the game got a short frame and
     a black hole under it, and the board shrank to 17px cells. The stage keeps an EXPLICIT height (100dvh minus the gutter).
     And the middle way on the gutter: 56px is the least that clears a ~95px bar once Butcher's own 28px bottom padding and
     the board's centring are counted (board bottom ≥ 112px above the layout bottom at 724 tall) — cells of 27, not 25. */
  /* CORRECTION 6 Sep 2026 00:20 (Sum, after the zoom fix: "better, zoom independent, still a bit of gutter"): the 56px was paid EVERYWHERE,
     and the only browser that needs it is the one whose bottom bar paints over the page — Chrome on iOS (and its siblings Firefox/Edge on iOS,
     the same WKWebView shape). Safari keeps its toolbar out of the viewport (the simulator's probe: inner 714 of an 874 glass), a desktop window
     has no bar, Android's bar rides on top and dvh already tracks it — so for all of them the 56 was 56px of dark under the game, "the price of
     never clipping" charged to people who were never going to clip. zoom.js (the glass predicates) stamps html[data-ios-bar="overlay"] from the
     user agent for the overlay browsers; only they keep the 56. Everyone else gets the home indicator or 8px, the same line landscape uses.
     Everything above about WHY Chrome iOS needs it is still true. (was: --play-gutter: 56px for every portrait.) */
  /* 00:30 — Sum: "remove across board, it was a pre-detect-phone fix" · "remove gutter padding on all". Zero, everywhere: portrait, landscape,
     installed. The reserve was built (3 Sep 10:45) before the hull could tell a phone from a window, for a bar that no longer covers the board.
     The game keeps its own 8px under the board (cabinet.js __OZ_DESK.bottom, now 8 in every orientation). The standalone and landscape
     overrides are commented, not deleted — the 42px middle path was measured on glass and may be wanted again one day. */
  html[data-game="play"] { --play-gutter: 0px; }
  /* 00:25, his call, one message later: "I am happy, is playable at any zoom now, and the gutter it was for no longer exists." So the 56 goes for
     the overlay browsers too. The stamp from zoom.js stays as the hook; this is the one line to bring back if Chrome on iOS ever hides the board's
     bottom under its bar again:
     html[data-game="play"][data-ios-bar="overlay"] { --play-gutter: 56px; } */
  /* 5 Sep 2026 night — DIVIDED BY THE ZOOM. `fixed` inside the transformed #zoom-root scales with the root (zoom.js), so the raw
     calc(100dvh - gutter) drew the stage z times too tall: 1.3 → 983px on an 812px glass (bottom clipped), 0.8 → 605px and a 207px black
     gutter under the game (his phone; his narrow Chrome window). --z is published by zoom.js applyZoom(); at 1 nothing changes.
     (was: height: calc(100dvh - var(--play-gutter))) */
  html[data-game="play"] .card[data-card="games"] .arc-stage { position: fixed; top: 0; left: 0; right: 0; bottom: auto; height: calc((100dvh - var(--play-gutter)) / var(--z, 1)); z-index: 60; background: #000; }
  html[data-game="play"] .card[data-card="games"] .arc-frame { width: 100%; height: 100%; }
  html[data-game="play"] .card[data-card="games"] .arc-hud { display: flex; top: env(safe-area-inset-top, 0px); }   /* 11:25 — flush with the game's 44px band, not 8px under it */

  /* (00:30, off — his 'on all') @media (display-mode: standalone) { html[data-game="play"] { --play-gutter: max(env(safe-area-inset-bottom), 16px); } } */

  /* 📐 LANDSCAPE PLAY (3 Sep 2026): 44px of a 393px-tall glass is too big a bite across the top, so the band becomes a COLUMN
     at the left edge — joystick over pause — clear of the notch; Chrome has no bottom bar sideways, so the gutter is the home
     indicator only. Games return to their wide layouts on their own (taller-than-wide is false). */
  @media (orientation: landscape) {
    /* (00:30, off — his 'on all') html[data-game="play"] { --play-gutter: max(env(safe-area-inset-bottom), 8px); } */
    /* ⚠ CORRECTION 3 Sep 14:50 — "no bottom bar sideways" is still true, but Chrome's TOP bar counts the same way the bottom one did
       at 10:45: 100dvh includes the ~74px strip under it, so the frame ran that far past the glass (Sum, sideways Ping: "bottom of box
       is still off screen in play — is there a way to just let it fill y?"). The court already fills 100dvh; what it lacked was the
       glass's real height. -webkit-touch-callout exists only on iOS WebKit, so this reserve is iPhone-only — the Pixel keeps its 8px
       (0zapk/NOTE-apk.md: the same space to grab back). An installed PWA has no bar, so standalone re-takes the 8px right after. */
    /* ⚠ CORRECTION 15:15 — WITHDRAWN. The 76px reserve doubled up with games.js fit(), which already pushes the stage down by
       visualViewport.offsetTop: Sum's next screenshot had the court 31px short with a black band under it ("we got gutter — keep
       the court on screen and lose the gutter"). fit() now takes the visual viewport's own top AND height whenever Chrome has shifted
       it, so no CSS reserve is needed here; the 8px line above stands for every landscape. The iOS-only @supports trick is still a
       good tool — it is just not this fix. Kept for the trail:
       @supports (-webkit-touch-callout: none) { html[data-game="play"] { --play-gutter: 76px; } }
       @media (display-mode: standalone) { html[data-game="play"] { --play-gutter: max(env(safe-area-inset-bottom), 8px); } } */
    /* ⚠ CORRECTION 15:40 — and vv.height lied too ("bottom is cut off again"), so the 15:15 withdrawal was half right: the reserve IS
       needed, the 76 was the wrong size. Sum: "find a middle path". 8 → 37px past the glass, 76 → 31px short: 42 lands within 3px.
       Same iOS-only trick, same standalone re-take. app/?probe prints the real numbers on the stage for the next tune. */
    /* (00:30, off — his 'on all'; the 42 was measured on glass 3 Sep 16:00, kept for the day it is wanted)
    @supports (-webkit-touch-callout: none) { html[data-game="play"] { --play-gutter: 42px; } }
    @media (display-mode: standalone) { html[data-game="play"] { --play-gutter: max(env(safe-area-inset-bottom), 8px); } } */
    html[data-game="play"] .card[data-card="games"] .arc-hud { top: 0; bottom: 0; height: auto; width: 44px; left: env(safe-area-inset-left, 0px); flex-direction: column; justify-content: flex-start; padding: 6px 0 0; gap: 6px; }
    html[data-game="play"] .card[data-card="games"] .arc-hud .arc-fab, html[data-game="play"] .card[data-card="games"] .arc-hud .arc-home { width: 40px; margin: 0 2px; }
    html[data-game="play"][data-game-side="left"] .card[data-card="games"] .arc-hud { left: auto; right: env(safe-area-inset-right, 0px); }   /* the player holds the left → the column steps to the right (Ping's ⇄) */
  }

  /* 📱 11 Sep 2026 — THE PHONE PASS AFTER THE FIRST DAY OUT (Sum, back from the appointment: "buttons need to work better, gear needs
     to be accessible on phone, the spacer between esp and HIL buttons needs to go, nurse needs to take up whole screen, keyboard
     blocks view of input… let's make phone experience better"). Five rules, all here, all hull-gated. */
  /* 1 · THUMB TARGETS. The voice strip and the bar head were desk-sized (26px). 44 is the floor on glass. */
  .ab-ear, .ab-speak, .ab-send, .ab-mode { min-height: 44px; min-width: 44px; padding: 6px 10px; justify-content: center; }
  .ab-ear svg, .ab-speak svg, .ab-send svg { width: 24px; height: 24px; }
  .ab-mode { font-size: 1em; padding: 6px 12px; }
  .ab-head > button, .ab-head .ab-esp, .ab-head .oz-perm-btn { min-height: 44px; min-width: 44px; }
  /* 2 · THE HEAD WITHOUT ITS SPACER. .ab-fill pushed the jewels to the far right; on a 393px glass that gap was the biggest thing in
     the row and the reason the head overflowed. Everything sits left-to-right with a gap, and the row scrolls if it must. */
  .ab-head .ab-fill { display: none; }
  .ab-head { gap: 8px; overflow-x: auto; scrollbar-width: none; }
  .ab-head::-webkit-scrollbar { display: none; }
  /* 3 · THE GEAR STAYS ON THE GLASS — ⚠ CORRECTION 11:45, same hour, his screenshot: WRONG GEAR. I pinned the TOP BAR's settings tile,
     which "scrolled just fine", and sticky on a tile that is not last in the row floated it over the other tiles. The gear he could not
     reach is the BOT's gear at the end of its head (.card-gearbox: model · voice), which desk.css's hide ladder REMOVES under 460px —
     so on every phone it was gone until he turned the phone sideways. The hull keeps it: shown, last, sticky at the head's right edge
     while the rest of the head scrolls under it. His ladder still holds for the others (＋ · esp · perm go first, stop never).
     (was: #topbar .tb-btn[data-card="gear"] { position: sticky; right: 0; … } — withdrawn) */
  /* ⚠ CORRECTION 11:55 — the sticky gear is WITHDRAWN too (his screenshot: it rode over the jewels and the red; "pinned scroll under
     does not work"). The real fix is the ladder's ORDER, and that lives in shell/desk.css now for every build: jewels go first, esp
     second, the gear stays until 290px. Nothing pinned, nothing scrolls under anything. Only the 44px icon remains from this rung. */
  .ab-head .ab-icon { width: 44px; min-width: 44px; }   /* desk.css gives the slot a 64px min — on glass that is 20px of nothing */
  .agent-bar:not(.collapsed) .card-gearbox { min-height: 44px; }
  /* 3b · THE TOP ROWS WERE NARROWER THAN THE GLASS (his screenshot: tiles clipped at ~70% of the width). max-width: 100vw (2 Sep) is
     measured BEFORE the eat-me zoom transform, so at any zoom but 1 the rows end short of, or past, the screen. The rows are children
     of the zoom root; its width is the glass by construction (the FLOOR rule below), so 100% of it is the right width, at every zoom. */
  #topbar, #subbar, #greenroom-bar { max-width: none; width: 100%; }
  /* 4 · THE VERTICAL DECK. Every open bar and card is already one screen tall; a swipe should LAND on one, not stop halfway. proximity,
     not mandatory, so a long chart still scrolls inside itself. (his riff: "swipe up to nurse, swipe down to clipboard") */
  #desk { scroll-snap-type: y proximity; }
  #desk .agent-bar:not(.collapsed), #desk .card:not(.collapsed) { scroll-snap-align: start; }
  /* 5 · THE KEYBOARD. shell/zoom.js writes --kb from the visual viewport; the open bar gives that much back so its input and voice
     strip rise above the keys instead of hiding under them. */
  /* 5b · ONE BOT = ONE SCREEN, REALLY (11:45, his screenshot: "nurse still isn't filling screen, see toto on bottom, is just a normal
     scroll between them"). 100dvh was the GLASS, but the bar starts UNDER the nav rows, so it ran ~250px past the fold and the next bar
     peeked in. The scroller is #desk and its height is the glass minus the nav; shell/zoom.js measures it (--desk-h, ResizeObserver, so
     chrome-away and rotation are followed) and each open pane is exactly that tall. Now a swipe lands on one bot, the snap has a
     page to snap to, and the cards get the same size. */
  #desk .agent-bar:not(.collapsed) { height: calc(var(--desk-h, 100dvh) - var(--kb, 0px)) !important; }
  #desk .card:not(.collapsed) { height: var(--desk-h, 100dvh) !important; }

  /* 6 · (WITHDRAWN 13:40, his word twice: "do not make sticky", "undo sticky on phone".) For the record: a sticky tab strip in the hull
     was tried for the Pixel and pulled the same minute. His ask instead: "make them readable and usable and findable, not blanks and
     broken icons" — the icon width fix in clipboard.css (0 → 32px on Android SVGs) is the part that stands. */

  /* 7 · THE CHART'S NAV, ONE LINE PER CONCERN (11 Sep 2026, 13:45, Sum on the Pixel: "provider is still cut off — another line, since
     it scrolls off screen once we start reading; add lines for each tab's local nav"). clipboard.css L437/439 make .clip-nav-l and
     .clip-subnav nowrap for the desk, where the sheet is wide; on a 375px glass the tabs, the seven provider chips and the print/eyes
     shared one row and the chips fell off the right edge. Here: the tabs on their own line, each tab's local nav (chips · pickers) on
     the next, wrapping as it needs, the print/eyes on the last. Not sticky — his word; it scrolls away with the sheet. */
  .card[data-card="clipboard"] .clip-stick { display: flex; flex-wrap: wrap; row-gap: 6px; }
  .card[data-card="clipboard"] .clip-nav-l { display: flex; flex-wrap: wrap; flex: 1 1 100%; row-gap: 6px; }
  .card[data-card="clipboard"] .clip-nav-l > .clip-tabs { display: flex; flex: 1 1 100%; }
  .card[data-card="clipboard"] .clip-nav-l > .clip-subnav { display: flex; flex-wrap: wrap; flex: 1 1 100%; row-gap: 6px; }
  .card[data-card="clipboard"] .clip-stick .clip-topline { display: flex; flex: 1 1 100%; }
  .card[data-card="clipboard"] .clip-stick .clip-acts { margin-left: 0; }

  /* the notch / dynamic island (standalone PWA draws under the status bar) */
  #topbar { padding-top: max(6px, env(safe-area-inset-top)); }
  #desk { padding-bottom: env(safe-area-inset-bottom); }
  /* iOS zooms any input under 16px on focus — hold the floor so the page never jumps */
  input, textarea, select { font-size: max(1em, 16px); }
}
