/* Phone layer of the browser edition (operator #10887 / #10888): page behaviour on touch screens, safe-area insets,
   the flight thumb keys (mobile/touch.js) and the portrait / landscape menu fixes.
   Sources: assets/tuning/mobile.json ui.safe_area_margin_px 12 (aura_jcs safe_area.rs: every menu screen inside the
   system bars and cutout, the HUD on the full window) and touch.screen_margin_px 24. Sizes in design px use --u
   (ui/kit.js), which on a phone landscape window equals the game's phone scale (menu_scale_factor 1.5 over the
   device pixel ratio). */
:root {
  --safe-t: max(env(safe-area-inset-top, 0px), 12px);
  --safe-r: max(env(safe-area-inset-right, 0px), 12px);
  --safe-b: max(env(safe-area-inset-bottom, 0px), 12px);
  --safe-l: max(env(safe-area-inset-left, 0px), 12px);
  /* Thumb key: at least 48 CSS px (Android touch target), larger on big screens. */
  --thumb: max(48px, calc(120 * var(--u)));
}

/* No page scroll, bounce, text selection, tap flash or double-tap / pinch zoom anywhere on the game. */
html, body { overscroll-behavior: none; height: 100%; height: 100dvh; touch-action: manipulation; -webkit-text-size-adjust: 100%; }
body.is-touch { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; }
#game { touch-action: none; }
/* WHAT: Browsers without dynamic viewport units (Chrome < 108, the A51's stock Chrome 93) size the canvas to the fixed
   layer box (the layout viewport) instead of ui.css 100vh. WHY: There 100vh is the viewport with the URL bar hidden
   (388 CSS px on the A51 landscape vs innerHeight 332), so the 833x332 drawing buffer was stretched 17 % vertically,
   its bottom 56 px sat under the URL bar (3D horizon and aim off the DOM HUD), and the compositor drew 17 % more canvas
   pixels every frame (MOBILE #4). Browsers with dvh keep ui.css unchanged. */
@supports not (height: 100dvh) {
  #game { width: 100%; height: 100%; }
}
body.is-touch .layer { height: calc(100 * var(--dvh)); }

/* Error card: near the top, centred (ui.css width and centring), under the shell keys band (FULLSCREEN, sound: 64 px),
   never over the bottom keys (Back / FLY); CLOSE or a tap closes it, RELOAD reloads (UI_UNIFORMITY.md R7). */
.errors { top: calc(var(--safe-t, 0px) + 64px); bottom: auto; max-height: 40vh; }
body.is-touch .errors-text { font-size: 14px; }

/* FULLSCREEN key: hidden where the Fullscreen API does not exist (iOS Safari, some in-app browsers). */
body.no-fullscreen #shell .shell-fs { display: none; }
body.is-touch #shell { --shell-edge: max(var(--safe-t), var(--safe-r)); }

/* ── flight thumb keys (mobile/touch.js) ── */
.touch { position: fixed; inset: 0; z-index: 9; pointer-events: none; font-family: var(--font-ui); }
.touch-group { position: absolute; display: flex; gap: 10px; pointer-events: none; }
.touch-tr { right: var(--safe-r); top: var(--safe-t); flex-direction: row-reverse; }
.touch-bl { left: var(--safe-l); bottom: var(--safe-b); flex-direction: column-reverse; }
/* Weapons on the right edge, stacked from just above the game HUD's flight computer, which owns the bottom-right corner.
   The computer's top edge is (296 + 24) design px × its frame scale min(39.6/64.3, h·0.274/296, w·0.30/540)
   (hud/computer.js layout, computer/layout.rs 849-877) above the window bottom: min(29.6 % of the height, 17.8 % of the
   width, 197 px). The old centred column (top 50 %, -35 %) put GUN / FLR over the computer's top row and its MODE key
   on a 412 px landscape phone (UI-FLIGHT #1 A51 landscape captures). */
.touch-br { right: var(--safe-r); bottom: calc(min(29.63 * var(--dvh), 17.78vw, 197px) + 8px); flex-direction: column-reverse;
  align-items: flex-end; }
/* Portrait: the computer takes half the width (hud/scale.js computerFrameScale, COMPUTER_PORTRAIT_WIDTH_SHARE 0.5), so
   its top edge is min(29.6 % of the height, 29.6 % of the width, 197 px) above the bottom (UI uniformity round 2). */
body.gm-portrait .touch-br { bottom: calc(min(29.63 * var(--dvh), 29.63vw, 197px) + 8px); }
/* Thumb keys = kit free-standing keys (button('solo'): JETS glass fill, hairline, panel radius; UI loop iteration 11) in
   place of the old menu_v2 9-slice key art. Here: thumb sizes, the per-action legend tint and the pressed look only.
   --hu 1 px: the in-flight root unit is ~0.33 px on a phone, which would square the key corners. No transition: a thumb
   key toggles .is-down many times a second in a fight, and an eased colour / shadow repaints the key for 150 ms each time.
   Sticky :hover after a tap (touch) keeps the action tint: the tint rules below carry the :hover / .is-focus states. */
.touch-key.jt-btn {
  --hu: 1px; pointer-events: auto; touch-action: none; margin: 0; padding: 0 12px; min-width: var(--thumb); height: var(--thumb);
  color: var(--white); font: inherit; font-size: max(14px, calc(26 * var(--u))); letter-spacing: .04em;
  opacity: .82; transition: none;
}
.touch-key.jt-btn:is(:hover, .is-focus) { color: var(--white);
  box-shadow: inset 0 0 0 1px var(--hg-line), inset 0 1px 0 var(--light-a10), 0 6px 18px var(--black-a35); }
.touch-key.jt-btn.is-down { opacity: 1; color: var(--cyan); background: var(--black-a30); }
.touch-gun { min-width: calc(var(--thumb) * 1.6); height: calc(var(--thumb) * 1.35); }
.touch-key.touch-gun:is(.jt-btn, :hover) { color: var(--amber); }
.touch-key.touch-missile:is(.jt-btn, :hover) { color: var(--red); }
.touch-key.touch-missile.is-empty:is(.jt-btn, :hover) { color: var(--hint); opacity: .45; }
.touch-boost { height: calc(var(--thumb) * 1.35); }
.touch-key.touch-boost:is(.jt-btn, :hover) { color: var(--green); }
.touch-key.jt-btn.is-down:is(.touch-gun, .touch-missile, .touch-boost) { color: var(--cyan); }
.touch-pause { min-width: var(--thumb); }
/* RECENTRE: never under the 48 px Android touch target (UI-FLIGHT #5: 0.8 x a 48 px thumb was 38.4 px tall). */
.touch-key.touch-recentre { font-size: max(12px, calc(20 * var(--u))); height: max(48px, calc(var(--thumb) * .8)); min-width: 0; }
/* The in-flight MUTE key (shell.css) takes the slot left of the pause key: RECENTRE starts one MUTE width (48) + gap further left. */
.touch-tr .touch-key.touch-recentre { margin-right: 58px; }
/* Portrait: keys sit lower and smaller so the centre stays free for the trackpad and the aim. */
body.gm-portrait .touch-key { --thumb: max(48px, calc(150 * var(--u))); }

/* ── HUD on a phone: the full window, flight scale (mobile.json ui.flight_scale_factor, hud_full_window) ── */
body.is-touch #hud { padding-top: var(--safe-t); }

/* ── menus inside the safe area (mobile.json ui.safe_area_margin_px) ── */
body.is-touch #settings.gm { padding: var(--safe-t) var(--safe-r) var(--safe-b) var(--safe-l); box-sizing: border-box; }

/* Hangar, portrait: the glass column fills the lower part of the screen (the jet preview keeps the top third), its
   panel unit --hu follows the column width (menu.css), rows never shorter than a 44 px touch target, Back / FLY
   reachable at the bottom inside the safe area. */
/* Short phones (A51: 775 px visible): the 34vh preview band left 468 px, two jet rows and the picked jet scrolled out of
   view. The column may then grow to 620 px (six rows, card, keys, service keys) while 120 px of preview stay on top. */
body.gm-portrait #menu .gm-hangar-col { height: auto;
  max-height: max(calc(100 * var(--dvh) - var(--safe-t) - var(--safe-b) - 34vh),
    min(620px, calc(100 * var(--dvh) - var(--safe-t) - var(--safe-b) - 120px)));
  bottom: var(--safe-b); left: var(--safe-l); right: var(--safe-r); width: auto;
  --hu: calc((100vw - var(--safe-l) - var(--safe-r)) / 500); --hg-row-min: max(44px, calc(56 * var(--hu))); }
body.gm-portrait #menu .gm-hangar-page { flex: 1 1 auto; }
/* Loading pill on the portrait JETS page: the column fills the lower screen, so the bottom-centre pill (loader.css)
   sat on the RANKING / PILOT / BUY THE GAME keys (A51, #13046). It moves to the top-left corner of the preview band,
   left of the MUTE / FULLSCREEN key row (shell.css, top right): the column's top edge is not a fixed height (Chrome 93
   on the A51 has no dvh, so a 34vh anchor landed on the first jet row). The label ellipsises instead of reaching the
   key row. The in-flight join bar and the choose-jet download bar (.jetload, menu.css) keep their own place. */
/* WHAT: The pill sits below the shell key row (sound, FULLSCREEN: top safe edge + 44 px key), not beside it.
   WHY:  Responsive UI check (2026-11-03): at 390-412 px wide the 52vw pill at the top edge covered the sound key. */
body.gm-portrait.gm-on-hangar .bgload:not(.bgload--join):not(.jetload) { bottom: auto; top: calc(var(--safe-t, 0px) + 64px);
  left: calc(var(--safe-l, 0px) + 12px); transform: none; min-width: 0; width: 52vw; box-sizing: border-box; }
body.gm-portrait.gm-on-hangar .bgload:not(.bgload--join):not(.jetload) .bgload-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Service keys on phones: a plain 34 px floor under the clamp() height (menu.css). The clamp once held a dvh term that
   Chrome 93 (A51) dropped, collapsing the MULTIPLAYER key to a 14 px strip; it reads var(--dvh) now (ui/tokens.css),
   the floor stays as the touch minimum. */
body.is-touch #menu .gm-hangar-extra .gm-hangar-multi { min-height: var(--touch-min); }
body.is-touch #menu .gm-hangar-row { min-height: max(44px, var(--hg-row-min)); }
body.is-touch #menu .gm-hangar-actions { min-height: 52px; }
/* Portrait card: compact (tighter padding and lines, no Mach / load chips) so all six rows fit without scrolling. */
body.gm-portrait #menu .gm-hangar-gauge { grid-template-columns: calc(96 * var(--hu)) minmax(0, 1fr) calc(120 * var(--hu)); }
body.gm-portrait #menu .gm-hangar-spec { padding: 8px 12px; gap: 6px; }
body.gm-portrait #menu .gm-hangar-spec-body { gap: 4px; }
body.gm-portrait #menu .gm-hangar-chips { display: none; }
body.gm-portrait #menu .gm-softkeys { padding-bottom: 8px; }
body.gm-portrait #menu .gm-hangar-col { gap: 8px; padding: 12px; }

/* Hangar, phone landscape: the column fills the safe height on the left with a floored panel unit; rows share it and
   the performance card yields (menu.css max-height rule). */
body.is-touch:not(.gm-portrait) #menu .gm-hangar-col { top: var(--safe-t); bottom: var(--safe-b); height: auto; max-height: none;
  left: var(--safe-l); --hu: max(var(--u), .5px); --hg-row-min: 44px; gap: 6px; padding: 8px 10px; }
body.is-touch:not(.gm-portrait) #menu .gm-softkeys { padding-bottom: 6px; }

/* Settings page: scrolls inside the safe area on short landscape screens. */
body.is-touch #settings.gm { overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; touch-action: pan-y; }
/* SETTINGS / CONTROLS never scroll as a page (menu.css .gm-fixed): only their list scrolls, inside the panel. */
body.is-touch #settings.gm.gm-fixed { overflow: hidden; }
body.is-touch .gm-page-col { max-width: 100%; }

/* Touch settings rows (Gyro aim, sensitivity) reuse the settings row kit; the value text sits between the arrows. */
.gm-gyro-value { min-width: calc(110 * var(--u)); text-align: center; }
/* JETS design-system pages on phones (operator 2026-11-01 pass): SETTINGS / CONTROLS, the multiplayer screens (RANKING,
   PILOT, ACCOUNT, BUY) and the result column size their text in --u, which a phone window makes tiny (412 px wide:
   ~0.21 px, 17 u = 4 px). Like the JETS column's --hu floor, these layers get their own panel unit from the window:
   portrait = width / 520 (17 u ≈ 13 px at 412 px), landscape = height / 620. --hu follows (.gm re-resolves it). */
body.is-touch.gm-portrait :is(#settings, #results, .mo-screen) { --u: calc((100vw - var(--safe-l, 0px) - var(--safe-r, 0px)) / 520); }
/* Landscape readability floor (UI loop iteration 10): height / 620 is 0.54 px on the A51 with the URL bar (332 px), so
   SETTINGS labels drew at 10 px and the 13 u small caps at 7 px. The unit never drops below 0.93 px: the smallest
   design text (13 u small caps) is 12 px and every key legend (16 u and up) at least 14.9 px. A landscape phone has the
   width for it (panels grow sideways, capped at the window by the .gm-page-col max-width) and these layers scroll
   vertically. Portrait keeps width / 520 (a 0.93 px unit broke the 412 px rows: words split, meters over labels); its
   small text gets per-class floors below instead. */
body.is-touch:not(.gm-portrait) :is(#settings, #results, .mo-screen) { --u: max(calc(100 * var(--dvh) / 620), .93px); }
/* Home account row on portrait phones: the portrait home's larger type (gm-text-body 32 u, language 40 u). */
body.gm-portrait #menu .gm-home-account { min-height: max(56px, calc(120 * var(--u))); grid-template-columns: calc(60 * var(--u)) minmax(0, 1fr) auto calc(24 * var(--u)); }
body.gm-portrait #menu .gm-home-account .gm-hangar-name { font-size: max(var(--font-min), calc(40 * var(--u))); }
body.gm-portrait #menu .gm-home-account .gm-hangar-sub { font-size: max(var(--font-min), calc(26 * var(--u))); white-space: normal; }
body.gm-portrait #menu .gm-home-account-cta { font-size: max(var(--font-min), calc(28 * var(--u))); padding: calc(12 * var(--u)) calc(20 * var(--u)); }
/* ── SETTINGS / CONTROLS on portrait phones (operator 2026-11-03 "controls panel and settings are completely broken on
   phone vertical") ──
   Measured defects (dev/layout-overflow-check.mjs, 360-412 px portrait): the 82vw page column left a 330 px panel, the
   label and a 360 u value column shared one line, so the Quality preset value ("Mobile Low (auto)", 111-117 px of text in
   a 55-77 px box) ran under its › arrow, labels split into two lines, the Tilt sensitivity label ran under its ‹ arrow,
   the four Radio chatter chips left their row, and on CONTROLS the three 150 u binding columns pushed the gamepad slots
   over the keyboard slots and past the row edge. Portrait layout:

     ┌ safe area ─────────────────────────────┐
     │                     [sound][FULLSCREEN]│  shell keys (shell.css), page starts under them
     │ [⚙] › SETTINGS                         │  fixed header
     │ [Graphics][Controls][Audio] →          │  tabs: one line, scroll sideways when more are added
     │ ┌ list (ScrollList, finger drag) ─────┐│
     │ │ ⚙ Quality preset                    ││  label line (wraps, never cut)
     │ │ [‹]   Desktop Medium (auto)    [›]  ││  value line: full width, arrows at the ends
     │ │ ⛶ Anti-aliasing                     ││
     │ │ [      On      ][      Off      ]   ││  chips share the width
     │ └─────────────────────────────────────┘│  fills the rest of the height
     │ [‹ Back                               ]│  footer: one row, always on screen
     └────────────────────────────────────────┘
   New rows built by the same row kit (valueRow: label + value) take this layout without a rule of their own. */
/* WHAT: The page column takes the full safe width (the layer already pads the safe-area insets), no side page padding,
         a 4 px vertical margin. WHY: The 82vw column and its 24 u page padding wasted 60-70 px of a 360-412 px phone. */
body.is-touch.gm-portrait #settings .gm-page { padding-left: 0; padding-right: 0; padding-top: 48px; }
body.is-touch.gm-portrait #settings .gm-page-col { width: 100%; max-width: 100%; margin: 4px 0; }
body.is-touch.gm-portrait #settings .gm-page.is-fixed > .gm-page-col { max-height: calc(100% - 8px); }
body.is-touch.gm-portrait #settings .gm-page-col.jt-panel { padding: 12px 10px; gap: 8px; }
/* WHAT: Tabs on one line that scrolls sideways (no bar), each tab at least its natural width, growing to fill the row
         when they fit (UI uniformity round 2: the four tabs left a gap at the row end). WHY: A fixed header of known
         height: wrapping tabs would grow it and shrink the list; more tabs stay reachable by a sideways swipe. */
body.is-touch.gm-portrait #settings .gm-tabs { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  touch-action: pan-x; overscroll-behavior-x: contain; }
body.is-touch.gm-portrait #settings .gm-tabs::-webkit-scrollbar { display: none; }
body.is-touch.gm-portrait #settings .gm-tab { flex: 1 0 auto; }
/* WHAT: Every value row stacks: glyph and label on the first line (the label wraps, never ellipsised), the value on a full
         width second line. WHY: Label and value never fit one 300 px line (see the header); stacked, every value has the
         row width and every label reads whole. */
body.is-touch.gm-portrait #settings .gm-vrow { flex-wrap: wrap; row-gap: 6px; column-gap: 8px; padding: 8px 10px; }
body.is-touch.gm-portrait #settings .gm-vrow-label { flex: 1 1 0; min-width: 0; white-space: normal; overflow-wrap: anywhere;
  text-overflow: clip; font-size: max(13px, calc(19 * var(--u))); }
body.is-touch.gm-portrait #settings .gm-vrow-value { flex: 1 1 100%; width: auto; max-width: 100%; min-width: 0; margin-left: 0;
  justify-content: space-between; gap: 8px; }
/* WHAT: The value text between the arrows takes the free width and wraps a long value ("Custom (based on Desktop
         Medium)") onto two centred lines. WHY: The fixed 240 u box clipped it under the › arrow. */
body.is-touch.gm-portrait #settings .gm-vrow-value .gm-cycle-value { flex: 1 1 0; width: auto; min-width: 0; white-space: normal;
  overflow-wrap: anywhere; line-height: 1.15; font-size: max(14px, calc(18 * var(--u))); }
body.is-touch.gm-portrait #settings .gm-arrow { flex: none; }
/* WHAT: On / Off and the multi-choice chips share the line equally, wrapping to a second line when four do not fit.
   WHY: The fixed 92 u chips left the four Radio chatter chips past the row edge. */
body.is-touch.gm-portrait #settings .gm-vrow-value { flex-wrap: wrap; }
body.is-touch.gm-portrait #settings .gm-vrow-value .gm-toggle { flex: 1 1 0; width: auto; min-width: 64px; padding: 0 8px; }
/* WHAT: Bus meters take the free width, their readout its text. WHY: Same row, no fixed widths. */
body.is-touch.gm-portrait #settings .gm-meter { flex: 1 1 0; min-width: 0; }
body.is-touch.gm-portrait #settings .gm-readout { width: auto; min-width: 48px; }
/* WHAT: Footer keys share one row (BACK, RELOAD NOW when shown) at the touch height; their inline design widths yield.
   WHY: The 220 u / 340 u keys wrapped into two rows and took 120 px under the list; one row keeps BACK on screen with
        more list height. */
body.is-touch.gm-portrait #settings .gm-page-actions { flex-wrap: nowrap; gap: 8px; }
body.is-touch.gm-portrait #settings .gm-page-actions .gm-pagekey { flex: 1 1 0; width: auto !important; min-width: 0;
  height: max(48px, calc(54 * var(--u))) !important; }
/* Footer legends wrap to two lines instead of an ellipsis at 360-390 px. */
body.is-touch.gm-portrait #settings .gm-page-actions .gm-kitkey-legend { white-space: normal; overflow-wrap: anywhere; line-height: 1.1;
  text-align: center; min-width: 0; }
/* WHAT: The reload notice key takes the line under its text. WHY: Its 260 u design width overflowed a narrow note. */
body.is-touch.gm-portrait #settings .gm-reload-note .gm-pagekey { width: 100% !important; height: var(--touch-min) !important; }
/* WHAT: CONTROLS bindings in portrait: the action name on its own line, then the keyboard / mouse slots and the gamepad
         slots in two equal columns; the head shows the two column names only. WHY: Three 150 u columns did not fit a
         300 px row (gamepad slots over the keyboard slots and past the row edge). */
body.is-touch.gm-portrait #settings .gm-bind-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); row-gap: 6px; column-gap: 8px;
  padding: 8px 10px; }
body.is-touch.gm-portrait #settings .gm-bind-row > .gm-bind-label { grid-column: 1 / -1; }
body.is-touch.gm-portrait #settings .gm-bind-head { padding: 0 10px; }
body.is-touch.gm-portrait #settings .gm-bind-head > .gm-bind-label { display: none; }
body.is-touch.gm-portrait #settings .gm-bind-keys { gap: 6px; }
body.is-touch.gm-portrait #settings .gm-bind-slot { flex: 1 1 auto; min-width: 0; padding: 0 6px; overflow: hidden; text-overflow: ellipsis; }
/* WHAT: Two-column tables of the Controls tab (Action | Keyboard / mouse, Action | Gamepad) keep the action and its keys on
         one line, the head shows both column names. WHY: Two columns fit a 300 px row; stacking them wasted a line each. */
body.is-touch.gm-portrait #settings .gm-bind-row.is-two > .gm-bind-label { grid-column: auto; }
body.is-touch.gm-portrait #settings .gm-bind-head.is-two > .gm-bind-label { display: block; }
body.is-touch.gm-portrait #settings .gm-bind-group { padding: 6px 10px 0; }
/* WHAT: Anchor strip, calibration key and live pad test in portrait: chips at the touch height, the key on its own full
         width line, the stick wells and button chips wrapping under the pad name. WHY: 300 px rows (card S32 portrait). */
body.is-touch.gm-portrait #settings .gm-anchors { gap: 6px; }
body.is-touch.gm-portrait #settings .gm-vrow-value .gm-ctl-key { flex: 1 1 100%; min-width: 0; }
body.is-touch.gm-portrait #settings .gm-pad-live { flex: 1 1 100%; gap: 8px; }
body.is-touch.gm-portrait #settings .gm-pad-test .gm-pad-name { flex: 1 1 100%; }
body.is-touch.gm-portrait #settings .gm-section-head { padding: 0 8px; }
/* Results on landscape phones: the column is the full window height, so its centred card reached the top edge under
   the MUTE / FULLSCREEN keys (shell.css, top right). Its top padding clears that key row. */
body.is-touch:not(.gm-portrait) #results .gm-results-col { padding-top: calc(var(--safe-t, 0px) + 60px); }
/* Results on landscape phones, one screen (UI-FLIGHT #1): the 33 vw column stacked header, score, stats and the three
   keys 600+ px tall, so RETRY / HANGAR / HOME sat below the fold of a 412 px landscape phone and needed a scroll. The
   column takes the safe width and the panel lays out as two columns (score | stats) with the keys in one row under
   them; the top padding still clears the MUTE / FULLSCREEN key row. Portrait and desktop keep the stacked column. */
body.is-touch:not(.gm-portrait) #results .gm-results-col { left: var(--safe-l); right: var(--safe-r); width: auto; min-width: 0;
  padding: calc(var(--safe-t, 0px) + 44px) 0 var(--safe-b, 0px); }
body.is-touch:not(.gm-portrait) #results .gm-results-housing { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas: "head head" "title stats" "keys keys"; column-gap: calc(12 * var(--u)); align-items: stretch; }
body.is-touch:not(.gm-portrait) #results .gm-results-head { grid-area: head; }
body.is-touch:not(.gm-portrait) #results .gm-results-screen.is-centered { grid-area: title; justify-content: center; }
body.is-touch:not(.gm-portrait) #results .gm-results-screen:not(.is-centered) { grid-area: stats; justify-content: center; }
body.is-touch:not(.gm-portrait) #results .gm-result-keys { grid-area: keys; flex-direction: row; }
body.is-touch:not(.gm-portrait) #results .gm-result-line { flex: 1 1 0; width: auto; height: calc(58 * var(--u)); }
/* Phones: SETTINGS / CONTROLS and the multiplayer screens (RANKING, PILOT, ACCOUNT, BUY) start under the MUTE /
   FULLSCREEN key row (shell.css, top right), which otherwise sat on the panel's › TITLE header ruler (portrait, and
   landscape since the iteration 10 unit floor widened the panels under the keys). */
body.is-touch :is(#settings, .mo-screen) .gm-page { padding-top: calc(var(--safe-t, 0px) + 56px); }
/* RUN PAUSED on phones (UI loop iteration 10): the root unit (0.33 px on the A51 landscape, 0.38 px portrait) drew its
   SCORE / sub lines at 7-8 px. The pause layer takes a 0.58 px floor (21 u captions = 12.2 px, 32 u keys = 18.6 px).
   The card is centred in the layer (menu.css); the safe-area padding keeps it off the notch, and the layer scrolls if a
   short window (URL bar shown) still cuts it. */
body.is-touch.gm-portrait #pause.gm { --u: max(calc(100vw / 1080), .58px); }
body.is-touch:not(.gm-portrait) #pause.gm { --u: max(min(calc(100vw / 1920), calc(100 * var(--dvh) / 1080)), .58px); }
body.is-touch #pause.gm { overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; touch-action: pan-y;
  padding: var(--safe-t) var(--safe-r) var(--safe-b) var(--safe-l); box-sizing: border-box; }
/* Portrait phones: the home READY TO FLY? and LANGUAGE headers follow the portrait home's larger type (keys and
   language row at 1.6x), so the JETS header reads like the NEWS and screen headers instead of a 28 u strip. */
body.gm-portrait #menu :is(.gm-home-head, .gm-langpick-header) { --hu: calc(1.6 * var(--u)); }
/* FULLSCREEN key legend on phones (UI loop iteration 10): 20 u of the root unit drew it at 6.6 px on the A51
   landscape; a key legend never drops under 14 px. */
body.is-touch #shell .shell-fs { font-size: max(14px, calc(20 * var(--u))); }
/* Portrait phones (UI loop iteration 10): SETTINGS / CONTROLS and the multiplayer screens keep the width / 520 unit,
   so their 13-16 u small text drew at 9.7-11.9 px on a 412 px phone. Per-class floors (design size unchanged above the
   floor): 12 px for captions, table heads, binding chips and notes, 14 px for tab / toggle / filter key legends. */
body.is-touch.gm-portrait :is(#settings, .mo-screen) :is(.gm-hangar-spec-title, .gm-group, .jt-table-head, .mo-bar-v) { font-size: max(12px, calc(13 * var(--hu))); }
body.is-touch.gm-portrait :is(#settings, .mo-screen) :is(.gm-nav-item, .gm-bind-head, .gm-capkey, .mo-pilot-id, .mo-stat-l, .gm-kitkey-sub, .mo-acct) { font-size: max(12px, calc(14 * var(--u))); }
body.is-touch.gm-portrait :is(#settings, .mo-screen) .mo-note { font-size: max(12px, calc(15 * var(--u))); }
body.is-touch.gm-portrait :is(#settings, .mo-screen) .gm-bind-slot { font-size: max(12px, calc(16 * var(--u))); }
body.is-touch.gm-portrait :is(#settings, .mo-screen) .gm-chipkey { font-size: max(14px, calc(16 * var(--u))); }
/* PILOT sorties on portrait phones: the time-ago line (.82 em) gets the 12 px floor, and KILLS / DEATHS take a little
   of the jet column so the 12 px DEATHS caption is not cut. */
body.is-touch.gm-portrait .mo-sortie-row > :first-child { font-size: max(12px, .82em); }
body.is-touch.gm-portrait .mo-sortie-row { grid-template-columns: minmax(0, 1.75fr) repeat(2, minmax(0, .95fr)) minmax(0, 1.2fr); }
/* Page key legends (CHANGE NAME, BACK, ...) inherit their key's size (18 u = 13.4 px portrait): 14 px floor. */
body.is-touch.gm-portrait :is(#settings, .mo-screen) .gm-kitkey-legend { font-size: max(14px, 1em); }

/* ── rotate cover (mobile/landscape.js, operator 2026-11-02 "mobile forced to landscape") ──
   Phones play in landscape: the first tap enters fullscreen and locks the orientation; where the lock is refused (iOS
   Safari, in-app browsers, fullscreen left) a portrait window shows this cover over the whole game. Shown by the media
   query alone (no script per rotation); flat (UI-latency contract): one fill, borders, no gradient, shadow or animation.
   z-index above every layer (shell keys 60, menus, toasts). */
.rotate-cover { display: none; }
@media (orientation: portrait) {
  body.jcs-landscape .rotate-cover { position: fixed; inset: 0; z-index: 2147483000; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 18px; padding: var(--safe-t) var(--safe-r) var(--safe-b) var(--safe-l);
    background: var(--night); color: var(--white); font-family: var(--font-ui); text-align: center; touch-action: none; }
}
/* Phone outline turned on its side (CSS borders): the gesture the player has to make, no image download. */
.rotate-phone { width: 92px; height: 52px; border: 3px solid var(--cyan); border-radius: calc(var(--r-card) * 1px); box-sizing: border-box;
  position: relative; }
.rotate-phone::after { content: ''; position: absolute; right: 6px; top: 50%; width: 4px; height: 16px; margin-top: -8px;
  border-radius: calc(var(--r-tick) * 1px); background: var(--cyan); }
.rotate-title { font-size: 22px; letter-spacing: .08em; color: var(--white); }
.rotate-sub { font-size: 15px; color: var(--cyan); letter-spacing: .03em; }
/* Portrait lock (Settings Orientation row, operator 2026-11-03): the same cover while a landscape window shows a page
   that wants portrait (body.jcs-portrait, mobile/landscape.js); the phone outline stands upright. */
@media (orientation: landscape) {
  body.jcs-portrait .rotate-cover { position: fixed; inset: 0; z-index: 2147483000; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 18px; padding: var(--safe-t) var(--safe-r) var(--safe-b) var(--safe-l);
    background: var(--night); color: var(--white); font-family: var(--font-ui); text-align: center; touch-action: none; }
}
body.jcs-portrait .rotate-phone { width: 52px; height: 92px; }
body.jcs-portrait .rotate-phone::after { right: 50%; top: auto; bottom: 6px; width: 16px; height: 4px; margin: 0 -8px 0 0; }
/* RETRY carries a sub line ("Fly this run again"): the primary key takes a larger share of the landscape key row. */
body.is-touch:not(.gm-portrait) #results .gm-result-line.is-primary { flex-grow: 1.4; }

/* ── Responsive UI on touch screens (operator 2026-11-03 "the UI NOT being responsive enough") ── */
/* WHAT: Settings tabs and value arrows are at least the 44 px touch target (ui.css --touch-min).
   WHY:  They drew 32-37 px on phones in landscape (34 u arrows, 40 u tabs). */
body.is-touch .gm-tab { height: max(var(--touch-min), calc(40 * var(--u))); }
/* WHAT: Controls anchors and section header keys are touch targets too. WHY: Card S32: each section is reached by a tap. */
body.is-touch #settings :is(.gm-anchor, .gm-section-head) { min-height: var(--touch-min); }
/* WHAT: Anchor chips are at least the touch target wide too. WHY: "Gyro" drew 43 px wide in short landscape. */
body.is-touch #settings .gm-anchor { min-width: var(--touch-min); }
body.is-touch .gm-arrow { width: max(var(--touch-min), calc(34 * var(--u))); height: max(var(--touch-min), calc(34 * var(--u))); }
/* WHAT: Settings value chips (On / Off) and page keys (Back, Controls) are at least the touch target too.
   WHY:  With the 0.7 px landscape unit below they drew 27 px (chips) and 38 px (page keys) tall. */
body.is-touch :is(#settings, .mo-screen) :is(.gm-toggle, .gm-pagekey) { min-height: var(--touch-min); }
/* WHAT: The keyboard hint line (Arrows / Enter / Esc) is hidden on touch screens.
   WHY:  A phone has no arrow keys; on a 360 px landscape window the line took the height the settings list needed. */
body.is-touch .gm-nav-hint { display: none; }
/* WHAT: Short landscape touch windows (phones in landscape): the settings page starts at the top safe edge, keeps the
         right side free for the shell keys (sound, FULLSCREEN) instead of a 56 px top band, and its panel unit floor
         drops from 0.93 to 0.7 px (text keeps the 11 px --font-min floor, keys the 44 px --touch-min).
   WHY:  At 780 x 360 the title, tabs and keys took the whole panel and the settings list was 0 px tall (live
         alpha-1.519.0): the settings could not be read or changed on a phone in landscape. */
@media (orientation: landscape) and (max-height: 599.98px) {
  body.is-touch:not(.gm-portrait) #settings { --u: max(calc(100 * var(--dvh) / 620), .7px); }
  body.is-touch:not(.gm-portrait) #settings .gm-page { padding-top: var(--safe-t); padding-right: calc(var(--safe-r) + min(230px, 30vw)); }
  /* WHAT: The fixed settings / controls column is a grid of the window height: the title shares its row with the tabs
           (settings) or the footer keys (controls), the list takes the rest. WHY: The tabs (or keys) row took 52 px of
           a 304 px column, leaving a 109-145 px list at 780 x 360 / 844 x 390 (one and a half rows, release 2 adds the
           Controls and Orientation rows); one header row gives the list about 50 px more. */
  body.is-touch:not(.gm-portrait) #settings .gm-page.is-fixed > .gm-page-col { display: grid; grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto minmax(0, 1fr); grid-auto-rows: auto; align-items: center; height: calc(100% - 28 * var(--u)); }
  body.is-touch:not(.gm-portrait) #settings .gm-page.is-fixed > .gm-page-col > * { grid-column: 1 / -1; }
  body.is-touch:not(.gm-portrait) #settings .gm-page.is-fixed > .gm-page-col > .jt-title { grid-column: 1; grid-row: 1; min-width: 0; }
  body.is-touch:not(.gm-portrait) #settings .gm-page.is-fixed > .gm-page-col > .gm-tabs:nth-child(2) { grid-column: 2; grid-row: 1; }
  body.is-touch:not(.gm-portrait) #settings .gm-page.is-fixed > .gm-page-col > .gm-oled-panel.is-fill { grid-row: 2; align-self: stretch; min-height: 0; }
  /* WHAT: Footer row 3: Back on the left, the Controls anchor strip on the right (the reload note, when shown, takes the
           next row). WHY: Card S32 anchors in their own row above the list left it 103-133 px tall at 780 x 360 /
           844 x 390; beside Back they cost no height. */
  body.is-touch:not(.gm-portrait) #settings .gm-page.is-fixed > .gm-page-col > .gm-page-actions { grid-column: 1; grid-row: 3; }
  body.is-touch:not(.gm-portrait) #settings .gm-page.is-fixed > .gm-page-col > .gm-anchors { grid-column: 2; grid-row: 3; justify-self: end; max-width: 100%; }
  /* WHAT: Value rows: the label keeps at least 150 u and wraps onto two lines instead of an ellipsis; a value that no
           longer fits beside it (the four Radio chatter chips) moves to its own line, right aligned; a long cycle value
           ("Custom (based on …)") wraps between its arrows. WHY: At 667 x 375 labels were cut ("Hold to keep turning"
           135 px in 100), Radio chatter's label was squeezed to 38 px and the Quality preset value overflowed its box. */
  body.is-touch:not(.gm-portrait) #settings .gm-vrow { flex-wrap: wrap; row-gap: calc(4 * var(--u)); }
  body.is-touch:not(.gm-portrait) #settings .gm-vrow-label { flex: 1 0 calc(150 * var(--u)); min-width: 0; white-space: normal;
    overflow-wrap: anywhere; text-overflow: clip; }
  body.is-touch:not(.gm-portrait) #settings .gm-vrow-value { min-width: 0; margin-left: auto; }
  body.is-touch:not(.gm-portrait) #settings .gm-vrow-value.is-wide { flex-wrap: wrap; justify-content: flex-end; max-width: 100%; }
  body.is-touch:not(.gm-portrait) #settings .gm-vrow-value .gm-cycle-value { white-space: normal; overflow-wrap: anywhere; line-height: 1.15; }
}

/* WHAT: The menu-time loading pill is hidden while the settings page is shown on a portrait or short landscape phone.
   WHY:  Responsive UI (operator 2026-11-03): there the settings list reaches the bottom edge and the bottom-centre pill
         covered its arrow and toggle keys (780 x 360, 412 x 915). Loading continues; the pill returns on the next screen. */
@media (orientation: portrait), (orientation: landscape) and (max-height: 599.98px) {
  body.gm-on-settings .bgload:not(.bgload--join):not(.jetload) { visibility: hidden; }
}

/* Phone flat contract (ui/flat.css): the header tick tape loses its tick gradients, which left only its ▼ pointer
   floating at the end of every panel header (UI uniformity round 2, portrait SETTINGS). The tape is decorative: hidden,
   its place kept, so the header layout is the same as with the ticks. */
body.ui-flat .gm-hangar-tape { visibility: hidden; }
