/* ══ THE PHONE SHELL ═══════════════════════════════════════════════════════════════════
   Scene-first phone layout. Every rule is SHELL-scoped, not engine-scoped.

   2026-08-23: these rules used to read html[data-renderer="lcc"], which quietly meant two
   different things at once — "the XGRIDS SDK is drawing" and "lay this out for a phone" —
   because the only phone route was the LCC one. Anthony asked for a phone that runs the
   DESKTOP renderer behind this SAME navigation, so the selector now asks the layout
   question only: html[data-shell="mobile"].

   Nothing about the LCC route changed. client_renderer_router.js pins data-shell="mobile"
   whenever the engine is lcc, so every rule below still lands exactly where it did.
   The desktop-only corrections in client.html are scoped
   html[data-renderer="spark"]:not([data-shell="mobile"]) for the mirror-image reason.  */
html[data-shell="mobile"],
html[data-shell="mobile"] body {
  width: 100%;
  height: 100%;
  min-height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}
html[data-shell="mobile"] body {
  position: fixed;
  inset: 0;
  margin: 0;
  background: #000;
  touch-action: none;
}

/* The desktop title and tool rows must never consume phone scene space. */
html[data-shell="mobile"] header { display: none !important; }
html[data-shell="mobile"] main {
  position: fixed;
  inset: 0;
  display: block;
  width: 100vw;
  height: 100dvh;
  min-height: 100%;
  overflow: hidden;
}
html[data-shell="mobile"] main > .view {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
}
html[data-shell="mobile"] #cv {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}
html[data-shell="mobile"] .view-stack { display: contents; }
html[data-shell="mobile"] .loading {
  z-index: 41;
  width: min(330px, calc(100vw - 32px));
  max-width: calc(100vw - 32px);
}

/* COMPONENTS AND DETAILS ARE BOTTOM SHEETS, AND THE SCAN IS ALWAYS VISIBLE ABOVE THEM.
   Anthony, 2026-08-08: "on mobile, it's very blocky and hard to use because the panels take
   up the whole screen... I would like to see the panel (when open) and the scene at the
   same time." He picked layout A and set the number himself: "keep it below 30% of the
   screen unless it's expanded."

   They used to be 88vw x 100dvh side drawers, which is why a phone showed a sliver of scan
   down one edge and nothing else. Now: 30% of the viewport, anchored to the bottom, with a
   grab handle that expands to 76% for reading a long record and drops back.

   30dvh is a CEILING, not a fixed height - a short record uses only what it needs, so the
   scan gets even more room. There is a floor of 148px so the sheet is still usable on a
   small phone in landscape, where 30dvh is about 100px.

   NOTHING HERE TOUCHES THE CAMERA OR THE HIGHLIGHTS. This is the panel geometry only. */
html[data-shell="mobile"] main > .list,
html[data-shell="mobile"] main > .doc {
  position: fixed !important;
  z-index: 45 !important;
  left: 0 !important;
  right: 0 !important;
  top: auto !important;
  bottom: 0 !important;
  width: 100% !important;
  height: auto !important;
  max-height: max(148px, 30dvh) !important;
  margin: 0 !important;
  padding: 22px 12px calc(10px + env(safe-area-inset-bottom)) !important;
  background: #fff !important;
  border: 0 !important;
  border-top: 3px solid #024b97 !important;
  border-radius: 10px 10px 0 0 !important;
  box-shadow: 0 -8px 26px rgba(17,38,58,.20) !important;
  overflow: auto !important;
  overscroll-behavior: contain;
  touch-action: pan-y;
  transition: transform 180ms ease, max-height 180ms ease;
  will-change: transform;
  transform: translateY(103%);
}
html[data-shell="mobile"] main > .list.mobile-open,
html[data-shell="mobile"] main > .doc.mobile-open { transform: translateY(0); }

/* Expanded, for reading a long record or scrolling 128 components. Still not the whole
   screen: the scan keeps the top quarter so you never lose your place in the building. */
html[data-shell="mobile"] main > .list.mobile-tall,
html[data-shell="mobile"] main > .doc.mobile-tall {
  max-height: 76dvh !important;
}

/* THE BOTTOM OF THE SCREEN IS NOW SHARED, SO THE WALKING CONTROLS GET OUT OF THE WAY.
   MEASURED on a 390x844 phone viewport: with the sheet at 30% the joystick, the up/down
   buttons and the FPV/Rotate/Start/More bar all sat ON TOP of the record and the text was
   unreadable. They never collided before because the panels were full-height side drawers.

   While a sheet is open: the joystick and the elevator go, because you cannot walk and read
   at the same time and they are the two biggest obstructions. The four-button bar STAYS -
   it is how you get to More and back to Start - so the sheet is inset above it instead of
   underneath it. Everything returns the moment the sheet closes. Nothing is deleted. */
body.mobile-panel-open .mobile-joystick,
body.mobile-panel-open .mobile-elevator { display: none !important; }
/* THE BAR'S REAL HEIGHT, MEASURED AT RUNTIME, NOT GUESSED. A hard-coded 46px cleared the bar
   on a 390x844 emulated phone and still ran UNDER it on Anthony's actual iPhone, where the
   safe-area inset and the label wrapping make the bar taller. The runtime publishes
   --rb-bar-h from getBoundingClientRect and re-measures on resize and rotation. The literal
   is only a fallback for the frame before the first measurement. */
html[data-shell="mobile"] body.mobile-panel-open main > .list,
html[data-shell="mobile"] body.mobile-panel-open main > .doc {
  bottom: calc(var(--rb-bar-h, 46px) + 6px) !important;
  padding-bottom: 8px !important;
  border-radius: 10px 10px 0 0 !important;
}
/* The bar sits above the sheet so its four buttons are never covered. */
html[data-shell="mobile"] .mobile-lcc-controls { z-index: 46; }

/* The grab handle. A real button, so it works by tap and by keyboard, not only by drag -
   the drag alone would be unreachable for anyone using a switch or a keyboard. */
.mobile-sheet-grip {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: grab;
  touch-action: none;
  z-index: 2;
}
.mobile-sheet-grip::before {
  content: '';
  width: 42px;
  height: 4px;
  border-radius: 2px;
  background: #c3ccd6;
}
.mobile-sheet-grip:focus-visible { outline: 2px solid #024b97; outline-offset: -3px; }
.mobile-sheet-grip[aria-expanded="true"]::before { background: #024b97; }

/* THE PANEL'S OWN FURNITURE WAS EATING THE PANEL. Anthony, on his phone, 2026-08-08:
   "the title takes up too much space, while the list is microscopic... there's no reason
   why a title should be that big, while the usable parts are microscopic."

   He was right and it was measured on his screenshots: inside a 253 px sheet, Components
   spent 44 px on a heading, 3 px on a rule and 30 px on a footer count before showing a
   single row - about a third of the panel on chrome, leaving room for two components out of
   128. Details spent even more: a full-width division badge, an ACCEPTED badge on its own
   line, a 23 px title, a subtitle and a decorative rule, all before the description.

   So on the phone route only: the heading collapses to one compact row that carries the
   count, the pills sit on the same line as each other, the title comes down to 15 px, and
   the two purely decorative rules go. Desktop keeps every one of them. */
html[data-shell="mobile"] main > .list > h2 {
  display: flex; align-items: center; gap: 8px;
  padding: 0 2px 6px !important; margin: 0 !important;
  font-size: 12px !important; line-height: 1.2 !important;
}
html[data-shell="mobile"] main > .list > h2 .list-n { font-size: 11px !important; margin: 0 !important; }
html[data-shell="mobile"] main > .list > h2 .list-filter {
  margin-left: auto !important; padding: 4px 8px !important; font-size: 10px !important;
  min-height: 28px !important;
}
html[data-shell="mobile"] main > .list > .rule { display: none !important; }
/* The footer count repeats what the heading already says, and it cost a whole row. */
html[data-shell="mobile"] main > .list > .count,
html[data-shell="mobile"] main > .doc .doc-rule { display: none !important; }
html[data-shell="mobile"] main > .doc .doc-h { padding: 0 !important; }
html[data-shell="mobile"] main > .doc .pills { gap: 5px !important; }
html[data-shell="mobile"] main > .doc .badge {
  font-size: 8.5px !important; letter-spacing: .5px !important; padding: 2px 5px !important;
}

/* CONDENSED, because the desktop type was being reused at desktop size on a phone.
   Anthony: "much more condensed panels - smaller type, tighter rows, less padding." */
html[data-shell="mobile"] main > .list .item { padding: 5px 8px !important; gap: 7px !important; }
html[data-shell="mobile"] main > .list .item .nm { font-size: 12px !important; line-height: 1.25 !important; }
html[data-shell="mobile"] main > .list .item .mt { font-size: 9.5px !important; line-height: 1.25 !important; }
html[data-shell="mobile"] main > .list .class-head { padding: 3px 8px !important; font-size: 9.5px !important; }
html[data-shell="mobile"] main > .doc .doc-h h1 { font-size: 15px !important; line-height: 1.15 !important; margin: 3px 0 1px !important; }
html[data-shell="mobile"] main > .doc .doc-h .sub { font-size: 10px !important; }
html[data-shell="mobile"] main > .doc section { margin: 8px 0 !important; }
html[data-shell="mobile"] main > .doc h3 { font-size: 10px !important; margin: 0 0 4px !important; }
html[data-shell="mobile"] main > .doc .desc,
html[data-shell="mobile"] main > .doc .kv { font-size: 11.5px !important; line-height: 1.4 !important; }
html[data-shell="mobile"] main > .doc .row { padding: 3px 0 !important; }

.mobile-edge-tabs {
  position: fixed;
  inset: 0;
  z-index: 44;
  pointer-events: none;
}
.mobile-edge-tab {
  position: absolute;
  top: 44%;
  min-width: 38px;
  min-height: 108px;
  padding: 10px 7px;
  border: 1px solid rgba(2,75,151,.55);
  background: rgba(255,255,255,.94);
  color: #024b97;
  font: 700 10px/1 Raleway, system-ui, sans-serif;
  letter-spacing: .2px;
  writing-mode: vertical-rl;
  pointer-events: auto;
  box-shadow: 0 2px 8px rgba(17,38,58,.14);
}
.mobile-edge-tab span { font-size: 16px; margin-bottom: 5px; }
.mobile-edge-tab-left {
  left: -1px;
  border-left: 0;
  border-radius: 0 2px 2px 0;
}
.mobile-edge-tab-right {
  right: -1px;
  border-right: 0;
  border-radius: 2px 0 0 2px;
  transform: rotate(180deg);
}
.mobile-edge-tab:disabled { opacity: .42; }
.mobile-edge-tab[aria-expanded="true"] { background: #024b97; color: #fff; }

/* Two direct appearance choices stay one tap away. */
/* WE CANNOT ASSUME THE CLIENT INSTALLED IT. Anthony, 2026-08-24, with a screenshot of the
   floor plan and the Original / Highlight pair sliced off at the top: "maybe you built this
   assuming that people are gonna use the full screen and download it as kind of like a Home
   screen app - well we can't assume that."
   He is right, and 8px was only ever safe in a standalone window. In an ordinary Safari tab
   the page has no viewport-fit=cover, so env(safe-area-inset-top) is 0, and the collapsing
   top toolbar sits over the first ~50px of the page — over this row. The floor plan used to
   survive that by accident, because it sat at 58px, BELOW the band; aligning it to the row on
   his instruction moved it INTO the band and cut both.
   So the offset is now a variable, and it answers the real question — is there browser chrome
   above us or not — instead of assuming there is not. */
/* 52px WAS TREATING THE SYMPTOM. It was added when the top row appeared sliced off, on the
   theory that browser chrome was covering it. The real cause was the stuck page zoom above:
   zoomed in, the top of a fixed row is simply outside what you can see, and no amount of
   padding fixes that. With the zoom fixed the row needs nothing but breathing room, and
   Anthony was right that 52px looked like "huge space... that is ugly".
   10px in a browser tab, where the page starts below the status bar anyway; the real notch
   inset when installed to the home screen, where the page does extend underneath it. One
   expression, correct on any phone or tablet, with nothing guessed. */
html[data-shell="mobile"] { --rb-top-safe: max(10px, env(safe-area-inset-top)); }
.mobile-lcc-top {
  position: fixed;
  z-index: 38;
  inset: var(--rb-top-safe) 8px auto;
  min-height: 44px;
  pointer-events: none;
}
.mobile-lcc-top > * { pointer-events: auto; }
/* CENTERED IN THE SPACE IT ACTUALLY HAS, NOT IN THE VIEWPORT. Anthony, 2026-08-24, from his
   phone: "can we make the top of the floorplan aligned with the Original / Highlight and
   Full screen buttons?" Raising the floor plan to that line is the easy half; the hard half
   is that it then shares the line. MEASURED: the pair is 178px and viewport-centered, so on
   his 440px screen its left edge lands at 131.5px and the 122px plan ends at 130px — a 2px
   gap, which reads as a mistake rather than a layout. At 393px they overlap by 26px.
   So the pair centers between the plan and the full-screen button instead. It stays visually
   centered, it can never touch either neighbour, and when the plan is switched off in More the
   band opens back up to the full width. --rb-map-clear is the plan's own width plus a gap,
   restated in landscape where the plan is narrower. */
.mobile-lcc-top { --rb-map-clear: 130px; }
.mobile-view-mode {
  position: absolute;
  left: var(--rb-map-clear);
  right: 54px;                 /* the full-screen icon (44px) plus a gap */
  top: 0;
  width: max-content;
  margin: 0 auto;              /* left+right+definite width = centered in the band */
  display: grid;
  grid-template-columns: 1fr 1fr;
  border: 1px solid #d2d2d2;
  border-radius: 2px;
  overflow: hidden;
}
/* Plan switched off in More: the band opens to the whole width and the pair goes back to
   true screen center, which is where it sat before this rule existed. `right: 0` too — with
   only `left` released it centered in a band still reserving the full-screen icon, landing
   27px left of center. MEASURED clear of that icon at 375, 440 and in landscape. */
body.mobile-map-hidden .mobile-view-mode { left: 0; right: 0; }
.mobile-view-mode button,
.mobile-fullscreen,
.mobile-lcc-controls button,
.mobile-more-sheet button {
  min-width: 44px;
  min-height: 44px;
  border: 0;
  border-radius: 0;
  background: rgba(255,255,255,.96);
  color: #212121;
  font: 600 11px/1.15 Raleway, system-ui, sans-serif;
}
.mobile-view-mode button { min-width: 88px; padding: 0 12px; }
.mobile-view-mode button + button { border-left: 1px solid #d2d2d2; }
/* ONE BUTTON, NOT A PAIR (2026-08-24). Anthony: "maybe instead of having original and
   highlights maybe we just make that one button... that way it doesn't take up two button
   spots." It is the same trade he chose for the phone's navigation button on 2026-08-11 and
   for the LCC bar before that: one control for two mutually exclusive modes, NAMING THE MODE
   YOU ARE IN. Half the width, and the top row stops being crowded on a small phone. */
.mobile-view-mode.mobile-view-one { grid-template-columns: 1fr; }
.mobile-view-mode.mobile-view-one button { min-width: 128px; }
.mobile-view-mode button[aria-pressed="true"],
.mobile-lcc-controls button[aria-pressed="true"],
.mobile-more-sheet button[aria-pressed="true"] {
  background: #024b97;
  color: #fff;
}
/* AN ICON, NOT A WORD. Anthony, 2026-08-24: "make the Full screen button more condensed?
   Maybe just an icon." It was already icon-only under 430px; his phone is 440px, so he was
   the one person seeing the label. Now it is icon-only everywhere — 120px of button becomes
   44px, which is also what buys the segmented pair its room above.
   The glyph is the desktop's own hand-drawn four corners, not a second invention: corners
   OUT to enter, IN to exit, the same swap client.html does. Losing the word means losing the
   "Exit" text that was the only sign you were already full screen, so the button takes the
   blue fill every other mobile toggle uses, and syncFullscreenControl() keeps the state in
   the aria-label for anyone who cannot see either. */
.mobile-fullscreen {
  position: absolute;
  right: 0;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid #d2d2d2;
  border-radius: 2px;
}
.mobile-fullscreen svg { width: 18px; height: 18px; display: block; }
.mobile-fullscreen .fs-exit { display: none; }
.mobile-fullscreen[aria-pressed="true"] { background: #024b97; color: #fff; border-color: #024b97; }
.mobile-fullscreen[aria-pressed="true"] .fs-enter { display: none; }
.mobile-fullscreen[aria-pressed="true"] .fs-exit { display: block; }
/* Compact XGRIDS-style movement controls stay over the scene, never beside it. */
.mobile-lcc-controls {
  position: fixed;
  z-index: 38;
  left: 8px;
  right: 8px;
  bottom: max(8px, env(safe-area-inset-bottom));
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 5px;
}
.mobile-lcc-controls button {
  padding: 5px 2px;
  border: 1px solid #d2d2d2;
  border-radius: 2px;
}
.mobile-lcc-controls .control-icon {
  display: block;
  margin-bottom: 3px;
  font-size: 18px;
  line-height: 18px;
}
.mobile-joystick {
  position: fixed;
  z-index: 37;
  left: max(16px, env(safe-area-inset-left));
  bottom: calc(74px + env(safe-area-inset-bottom));
  width: 92px;
  height: 92px;
  border: 1px solid rgba(2,75,151,.5);
  border-radius: 46px;
  background: rgba(255,255,255,.38);
  backdrop-filter: blur(4px);
  touch-action: none;
}
.mobile-joystick-knob {
  position: absolute;
  left: 26px;
  top: 26px;
  width: 40px;
  height: 40px;
  border-radius: 20px;
  background: #024b97;
  box-shadow: 0 2px 8px rgba(0,0,0,.24);
  transform: translate(0,0);
}

.mobile-elevator {
  position: fixed;
  z-index: 37;
  right: max(16px, env(safe-area-inset-right));
  bottom: calc(74px + env(safe-area-inset-bottom));
  width: 44px;
  display: grid;
  grid-template-rows: 44px 44px;
  gap: 5px;
  border: 0;
  background: transparent;
}
.mobile-elevator button {
  min-width: 44px;
  min-height: 44px;
  border: 1px solid rgba(2,75,151,.62);
  border-radius: 50%;
  background: rgba(255,255,255,.95);
  color: #024b97;
  font: 800 22px/1 Raleway, system-ui, sans-serif;
  touch-action: none;
}
.mobile-elevator button[aria-pressed="true"] { background: #024b97; color: #fff; }
.mobile-elevator button:disabled { color: #747474; opacity: .48; }

/* Keep the proven building + user map compact. It can be hidden from More. */
html[data-shell="mobile"] .client-minimap {
  display: block;
  position: fixed;
  z-index: 34;
  left: max(8px, env(safe-area-inset-left));
  /* FLUSH WITH THE TOP ROW, his 2026-08-24 ask. Was 58px, which dropped it half a control
     below the buttons beside it. Same variable as .mobile-lcc-top, so they cannot drift and
     so both clear the browser's own chrome — see the note there. */
  top: var(--rb-top-safe);
  width: 122px;
  height: 86px;
  margin: 0;
  background: rgba(255,255,255,.86);
  border: 1px solid rgba(2,75,151,.32);
  pointer-events: none;
  transition: opacity 180ms ease;
}
html[data-shell="mobile"] .client-minimap-head { height: 23px; padding: 0 7px; }
html[data-shell="mobile"] .client-minimap-state { font-size: 8px; }
html[data-shell="mobile"] .client-minimap canvas { height: 61px; }
html[data-shell="mobile"] body.mobile-map-hidden .client-minimap { display: none; }

/* Secondary tools rise over the scene and disappear again; they never resize it. */
html[data-shell="mobile"] .height-panel,
html[data-shell="mobile"] .object-intel,
.mobile-more-sheet,
.mobile-install-sheet {
  position: fixed !important;
  z-index: 46 !important;
  left: 0 !important;
  right: 0 !important;
  top: auto !important;
  bottom: 0 !important;
  width: auto !important;
  height: min(70vh, 620px) !important;
  max-height: calc(100dvh - 56px) !important;
  margin: 0 !important;
  padding-bottom: calc(16px + env(safe-area-inset-bottom)) !important;
  border: 0 !important;
  border-top: 3px solid #024b97 !important;
  background: #fff !important;
  box-shadow: 0 -10px 30px rgba(17,38,58,.18) !important;
  overflow: auto !important;
  overscroll-behavior: contain;
  touch-action: pan-y;
  transform: translateY(105%);
  transition: transform 180ms ease;
}
html[data-shell="mobile"] .height-panel.mobile-open,
html[data-shell="mobile"] .object-intel.mobile-open,
.mobile-more-sheet.mobile-open,
.mobile-install-sheet.mobile-open { transform: translateY(0); }
html[data-shell="mobile"] .height-panel,
html[data-shell="mobile"] .object-intel { display: block; }

/* ── THE COPILOT IS ITS OWN SHEET, AND THE COMPONENTS DRAWER IS ALL COMPONENTS AGAIN ──
   Operator, 2026-08-11: *"move AI Copilot so that it's a button, and that way all the
   components pop out from the tab the way they did before. Because right now the components
   list is basically collapsed by default. I don't want that."*

   MEASURED before this change, portrait 393x852: the drawer was 256 px, the Copilot took
   180 px of it - 70% - and ZERO of the 128 component rows were fully visible. Yesterday's fix
   for "the Copilot is invisible" had solved that by spending the list's whole panel on it.

   The Copilot now opens over the scene like More and Hide ceiling do, sized to its content
   rather than to a fixed fraction, so a short box does not reserve half a phone. It only
   works because the node is reparented out of .list at wire-up: a position:fixed panel inside
   a transformed ancestor resolves against that ancestor, not the viewport. */

/* 2026-08-11: the in-flow block that used to sit here - which pinned the Copilot to the top
   of the Components drawer - is gone. It fixed the Copilot being invisible and cost the list
   70% of its panel and every one of its visible rows. The Copilot is a sheet again, above. */

html[data-shell="mobile"] .height-head,
html[data-shell="mobile"] .object-head { position: static; cursor: default; }
html[data-shell="mobile"] .height-head button,
html[data-shell="mobile"] .object-head button { min-width: 44px; min-height: 44px; }
/* Sized to its content with a ceiling, not to a fixed fraction - a short question box must
   not reserve half a phone. 56dvh leaves the scan visible above it while giving an answer
   room to breathe. */
html[data-shell="mobile"] .object-intel {
  height: auto !important;
  max-height: min(56dvh, 420px) !important;
  overflow: hidden !important;
}
html[data-shell="mobile"] .object-intel-body {
  display: flex;
  flex-direction: column;
  max-height: calc(min(56dvh, 420px) - 44px);
  overflow: auto;
}
/* THE BAR STAYS REACHABLE WHILE THE COPILOT IS OPEN, and one of the four buttons it holds is
   the AI Copilot button that closes it again. This stylesheet already made that rule for the
   Components and Details sheets and gave the reason - "the four-button bar STAYS... so the
   sheet is inset above it instead of underneath it" - and the Copilot has to obey it for the
   same reason, now that opening it is a bar action rather than a More entry. Measured: without
   this the sheet sits at bottom 0 and covers all four. */
html[data-shell="mobile"] .object-intel.mobile-open {
  bottom: calc(var(--rb-bar-h, 46px) + 6px) !important;
}
html[data-shell="mobile"] .scene-command {
  position: sticky;
  top: 0;
  z-index: 2;
  grid-template-columns: minmax(0,1fr) 58px 64px;
  min-height: 74px;
  background: #fff;
  border-bottom: 1px solid #e0dede;
}
html[data-shell="mobile"] .scene-command textarea {
  min-height: 68px !important;
  max-height: 112px !important;
  padding: 10px !important;
  overflow: auto !important;
  /* 16px, NOT 15px, AND THE ONE PIXEL IS THE WHOLE BUG. See the note at the foot of this
     file: iOS Safari auto-zooms the page whenever a focused field computes under 16px, and
     often never zooms back. This was 15px, so every use of the AI Copilot — the control he
     reaches for most — could leave the phone stuck zoomed, and Safari remembers it per site,
     which is why reloading did not clear it. */
  font-size: 16px !important;
}
html[data-shell="mobile"] .scene-command button {
  min-width: 0;
  min-height: 44px;
  padding: 0 7px;
}
.mobile-more-sheet { padding: 12px 14px calc(16px + env(safe-area-inset-bottom)); }
.mobile-more-sheet { height: auto !important; max-height: 50dvh !important; }
.mobile-more-sheet h2 { margin: 0 0 8px; font: 800 15px Raleway, system-ui, sans-serif; }
/* TWO COLUMNS AND EIGHT BUTTONS = FOUR EVEN ROWS. Anthony, 2026-08-24: "I would like it so
   that they're the rows and columns are uniformed and it does not separate row just for like
   one button." Three columns held nine; taking out Collision (it ships disabled — a button
   that cannot be pressed) and Walk/Rotate (his word: irrelevant) and moving Full screen in
   here leaves eight, and eight in three columns ends on a row of two. Two columns divides it
   exactly and gives every label room to sit on one line. */
.mobile-more-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; }
.mobile-more-sheet button { min-height: 44px; padding: 5px; border: 1px solid #d2d2d2; border-radius: 2px; font-size: 9.5px; }
html[data-shell="mobile"] .height-panel { height: min(46dvh, 360px) !important; max-height: 46dvh !important; }
html[data-shell="mobile"] .height-panel .cutaway-intro,
html[data-shell="mobile"] .height-panel .cut-mode-title { display: none; }
html[data-shell="mobile"] .height-panel .height-body { padding-top: 6px; }
html[data-shell="mobile"] .height-panel .cut-mode-switch { margin-top: 6px; }

.mobile-find-nav {
  position: fixed;
  z-index: 43;
  left: 50%;
  bottom: calc(62px + env(safe-area-inset-bottom));
  width: min(520px, calc(100vw - 124px));
  min-height: 44px;
  display: grid;
  grid-template-columns: 40px minmax(0,1fr) 40px 36px;
  transform: translateX(-50%);
  border: 1px solid rgba(2,75,151,.55);
  background: rgba(255,255,255,.96);
  box-shadow: 0 2px 10px rgba(0,0,0,.18);
}
.mobile-find-nav[hidden] { display: none; }
.mobile-find-nav button { min-width: 36px; min-height: 44px; border: 0; border-left: 1px solid #e0dede; background: transparent; color: #024b97; font: 800 22px/1 Raleway, system-ui, sans-serif; }
.mobile-find-nav button:first-child { border-left: 0; }
.mobile-find-nav .mobile-find-current { overflow: hidden; padding: 4px 7px; color: #212121; font: 600 9.5px/1.25 Raleway, system-ui, sans-serif; }
html[data-shell="mobile"] .pin.find-match { transform: translate(-50%,-50%) scale(1.35); outline: 3px solid rgba(2,75,151,.46); outline-offset: 2px; }
.mobile-install-sheet {
  height: auto !important;
  min-height: 210px;
  padding: 20px 22px calc(22px + env(safe-area-inset-bottom)) !important;
}
.mobile-install-sheet h2 { margin: 0 42px 12px 0; font: 800 20px Raleway, system-ui, sans-serif; }
.mobile-install-sheet p { margin: 10px 0; font: 14px/1.45 Open Sans, system-ui, sans-serif; }
.mobile-install-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 44px;
  height: 44px;
  border: 0;
  background: transparent;
  color: #333;
  font-size: 28px;
}

.mobile-lcc-error {
  position: fixed;
  z-index: 60;
  inset: auto 14px calc(80px + env(safe-area-inset-bottom));
  padding: 14px;
  border-left: 3px solid #b00020;
  background: #fff;
  box-shadow: 0 5px 24px rgba(0,0,0,.2);
  font: 13px/1.45 Open Sans, system-ui, sans-serif;
  touch-action: pan-y;
}
.mobile-lcc-error button {
  min-height: 44px;
  margin-top: 10px;
  padding: 0 14px;
  border: 0;
  border-radius: 2px;
  background: #024b97;
  color: #fff;
}

@media (orientation: landscape) and (max-height: 600px) {
  .mobile-edge-tab-right { right: 60px; }
  .mobile-lcc-controls {
    left: auto;
    right: max(6px, env(safe-area-inset-right));
    top: calc(58px + env(safe-area-inset-top));
    bottom: auto;
    width: 54px;
    display: flex;
    flex-direction: column;
  }
  .mobile-lcc-controls button { min-width: 50px; min-height: 46px; font-size: 9px; }
  .mobile-lcc-controls .control-icon { margin: 0; font-size: 15px; }
  .mobile-joystick { bottom: max(14px, env(safe-area-inset-bottom)); }
  .mobile-elevator {
    right: calc(66px + env(safe-area-inset-right));
    top: calc(58px + env(safe-area-inset-top));
    bottom: auto;
  }
  /* THE FLOOR PLAN, PLACED FOR THE ORIENTATION RATHER THAN INHERITED FROM THE OTHER ONE.
     Anthony, 2026-08-10: *"the floorplan map needs better positioning that adjusts based on
     the rotation."*

     Two measured faults, both only visible sideways:

       1. IT WAS INSET 46 px FROM THE LEFT while every other control hugs its edge, so the
          left side read as ragged. That inset was mine, from earlier the same day, and it
          was over-cautious: I moved it clear of the 38 px Components tab horizontally when
          the two never meet VERTICALLY. Measured at 852x393 the map occupies y 58-144 and
          the tab y 173-283 - a 29 px gap. Portrait is the same story, tab at y 375. So it
          goes flush to the corner at 8 px in BOTH orientations, which is also what portrait
          always did.
       2. THE SAME BOX COSTS FAR MORE SIDEWAYS. 122x86 is 3.1% of the screen either way, but
          it is 10% of portrait's 852 px of height and 22% of landscape's 393 px - and height
          is the whole problem in landscape. So it gets shorter here, and only here.

     It still steps aside entirely while a drawer is open, the rule the joystick, the elevator
     and the top bar already follow. */
  html[data-shell="mobile"] .client-minimap {
    left: max(8px, env(safe-area-inset-left));
    top: var(--rb-top-safe);                     /* same top line as portrait, 2026-08-24 */
    width: 104px;
    height: 62px;
  }
  /* The plan is 104px here, so the pair needs less clearance than portrait's 130px. */
  .mobile-lcc-top { --rb-map-clear: 112px; }
  html[data-shell="mobile"] .client-minimap canvas { height: 39px; }
  html[data-shell="mobile"] .client-minimap-head { height: 21px; padding: 0 6px; }
  html[data-shell="mobile"] body.mobile-panel-open .client-minimap { display: none !important; }
  /* ── PANELS GO BACK TO BEING SIDE DRAWERS WHEN THE PHONE IS ON ITS SIDE ──────────
     Anthony, 2026-08-10: "In the mobile version, the panels look terrible when you hold it
     horizontally... When I hold the phone straight up, it looks good."

     MEASURED on an 852x393 landscape viewport against the same build in 393x852 portrait:

       Components sheet    portrait 393 x 256, 9 of 138 rows readable
                           landscape 420 x 148, 6 of 138 rows readable, and a dead
                           432 px of viewport to its right
       More sheet          portrait 251 px tall for 251 px of content
                           landscape 197 px tall for ~140 px of content, the surplus
                           blank, and it covered the bottom 60 px of the right-hand
                           control column - including the More button that opened it
       Floor plan          portrait tucked at left 8 px; landscape pushed to left 130 px,
                           floating in mid-scene and then clipped by the sheet

     The bottom sheet is the right answer in portrait and the wrong one here, and the file
     already said why at the top of this stylesheet: "They never collided before because the
     panels were full-height side drawers." In landscape HEIGHT is the scarce resource and
     WIDTH is abundant, so a bottom sheet spends exactly the thing there is least of. These
     rules restore the side drawer for landscape only. Portrait is untouched - every rule
     here is inside the orientation query. */
  html[data-shell="mobile"] main > .list,
  html[data-shell="mobile"] main > .doc {
    top: 0 !important;
    bottom: 0 !important;
    height: 100dvh !important;
    max-height: 100dvh !important;
    width: min(400px, 46vw) !important;
    padding: 12px 12px calc(10px + env(safe-area-inset-bottom)) !important;
    border-top: 0 !important;
    border-radius: 0 !important;
  }
  html[data-shell="mobile"] main > .list {
    left: 0 !important;
    right: auto !important;
    border-right: 3px solid #024b97 !important;
    box-shadow: 8px 0 26px rgba(17,38,58,.20) !important;
    transform: translateX(-103%);
  }
  html[data-shell="mobile"] main > .doc {
    left: auto !important;
    right: 0 !important;
    border-left: 3px solid #024b97 !important;
    box-shadow: -8px 0 26px rgba(17,38,58,.20) !important;
    transform: translateX(103%);
  }
  html[data-shell="mobile"] main > .list.mobile-open,
  html[data-shell="mobile"] main > .doc.mobile-open { transform: translateX(0) !important; }
  /* mobile-tall raises a bottom sheet from 30dvh to 76dvh. A drawer is already full height,
     so the class has nothing left to do and must not shrink it. */
  html[data-shell="mobile"] main > .list.mobile-tall,
  html[data-shell="mobile"] main > .doc.mobile-tall { max-height: 100dvh !important; }
  /* THE SAME INSET PORTRAIT ALREADY USES, ROTATED. Portrait holds the sheet above the
     horizontal bar with --rb-bar-h. Here the identical bar is a vertical column on the right
     edge, so the right-hand drawer stops beside it using --rb-bar-w, which the runtime
     measures off the same element. Without this the column floats on top of the record,
     because it deliberately carries a higher z-index than the panels. */
  html[data-shell="mobile"] body.mobile-panel-open main > .list,
  html[data-shell="mobile"] body.mobile-panel-open main > .doc {
    bottom: 0 !important;
    padding-bottom: calc(10px + env(safe-area-inset-bottom)) !important;
    border-radius: 0 !important;
  }
  html[data-shell="mobile"] body.mobile-panel-open main > .doc {
    right: calc(var(--rb-bar-w, 60px) + 6px) !important;
  }
  /* The grab handle drags a sheet up and down. A side drawer has no vertical travel, so it
     is meaningless here - and it was eating 22 px off the top of the list. */
  html[data-shell="mobile"] main > .list > .mobile-sheet-grip,
  html[data-shell="mobile"] main > .doc > .mobile-sheet-grip { display: none !important; }

  /* THE FLOATING TOP BAR STEPS ASIDE WHILE A DRAWER IS OPEN, and it has to, because it
     cannot simply be ordered behind one. MEASURED, not reasoned about: with the drawer at
     z-index 45 and the bar at 38, elementFromPoint(360, 30) still returned the bar's button.
     `main` is position:fixed, which in Chrome opens a stacking context even at z-index auto,
     so the drawer's 45 only ranks it INSIDE main while the bar competes with main itself at
     the root. Raising main above the bar would drag the canvas up with it and bury the bar
     completely. So the bar hides, exactly the way the joystick, the elevator and the floor
     plan already do - you are reading a record, not recoloring the scan - and every one of
     them comes back the moment the drawer closes. Portrait never hit this: the bar sits at
     y 8-52 and the sheet at y 536, so they never touched. */
  html[data-shell="mobile"] body.mobile-panel-open .mobile-lcc-top { display: none !important; }

  /* The edge tab is the handle that closes the drawer, so it must stay. It just moves to the
     drawer's OUTER edge instead of sitting on top of the list it opened. :has() is a
     progressive enhancement - where it is unsupported the tab stays where it is today, which
     is the behavior that already ships. */
  html[data-shell="mobile"] body:has(main > .list.mobile-open) .mobile-edge-tab-left {
    left: min(400px, 46vw);
  }
  html[data-shell="mobile"] body:has(main > .doc.mobile-open) .mobile-edge-tab-right {
    right: calc(min(400px, 46vw) + var(--rb-bar-w, 60px) + 6px);
  }

  /* .object-intel is back in this group from 2026-08-11: the Copilot is its own bottom sheet
     again, reparented out of .list so `fixed` means fixed to the viewport. */
  html[data-shell="mobile"] .height-panel,
  html[data-shell="mobile"] .object-intel,
  .mobile-more-sheet,
  .mobile-install-sheet {
    left: 0 !important;
    right: calc(var(--rb-bar-w, 60px) + 6px) !important;
    top: auto !important;
    bottom: 0 !important;
    width: auto !important;
    /* Sized to its content, capped - not a flat 50dvh. On a 393 px-tall viewport the flat
       value spent 197 px on ~140 px of buttons and covered the control column with the rest. */
    height: auto !important;
    max-height: 76dvh !important;
    transform: translateY(105%);
    border-top: 3px solid #024b97 !important;
    border-right: 0 !important;
  }
  html[data-shell="mobile"] .height-panel.mobile-open,
  html[data-shell="mobile"] .object-intel.mobile-open,
  .mobile-more-sheet.mobile-open,
  .mobile-install-sheet.mobile-open { transform: translateY(0); }
  /* Sideways the bar is a COLUMN ON THE RIGHT, so the sheet clears it with the horizontal
     --rb-bar-w inset above and must NOT also reserve the bar's height at the bottom. Measured:
     the portrait rule was costing 52 px of a 393 px viewport for a bar that is not down there. */
  html[data-shell="mobile"] .object-intel.mobile-open { bottom: 0 !important; }
  .mobile-more-grid { grid-template-columns: repeat(5, 1fr); }
  .mobile-more-sheet button { min-height: 44px; }
  .mobile-find-nav { bottom: max(8px, env(safe-area-inset-bottom)); width: min(560px, calc(100vw - 150px)); }
}

/* THE BAND IS TIGHTEST ON THE SMALLEST PHONES. MEASURED with the plan on the top line: at
   375px the space between it and the full-screen icon is 175px against a 178px pair. 72px
   segments make it 146px and it centers with room at 375, 393 and 430; above 430 the pair
   keeps its full 88px segments, so his own 440px screen is untouched. */
@media (max-width: 430px) {
  .mobile-view-mode button { min-width: 72px; padding: 0 8px; }
}

@media (prefers-reduced-motion: reduce) {
  html[data-shell="mobile"] main > .list,
  html[data-shell="mobile"] main > .doc,
  html[data-shell="mobile"] .height-panel,
  html[data-shell="mobile"] .object-intel,
  .mobile-more-sheet,
  .mobile-install-sheet { transition: none; }
}
html.qa-no-motion[data-shell="mobile"] main > .list,
html.qa-no-motion[data-shell="mobile"] main > .doc,
html.qa-no-motion[data-shell="mobile"] .height-panel,
html.qa-no-motion[data-shell="mobile"] .object-intel,
html.qa-no-motion[data-shell="mobile"] .mobile-more-sheet,
html.qa-no-motion[data-shell="mobile"] .mobile-install-sheet { transition: none !important; }

/* ══ THE PHONE'S OWN TUTORIAL (2026-08-24) ═════════════════════════════════════════════════
   Every class here is prefixed .mtour- and appears nowhere else in the project, so none of it
   can reach the desktop card or the desktop guided tour. See app/client_mobile_tutorial.js
   for why the phone needed its own: the desktop card anchors to a toolbar button that is
   display:none on a phone, and describes a mouse and a toolbar that are not there.

   The scrim is one sheet, not the desktop tour's four rectangles, because the ring here is
   drawn ON TOP rather than being a hole — a phone control is 44px and a cut-out that size
   reads as damage. The ring is a 2px Rubicon blue outline with a white halo so it is visible
   over both the scan and the white bar. */
.mtour { position: fixed; inset: 0; z-index: 90; display: none; }
.mtour.mtour-on { display: block; }
.mtour-scrim { position: fixed; inset: 0; background: rgba(33,33,33,.55); }
/* FIXED, NOT ABSOLUTE, and that is a correctness fix rather than a preference. The ring is
   placed from getBoundingClientRect(), which reports against the viewport; anchoring it to a
   container instead means any offset between that container and the viewport becomes a
   constant error in the ring. Fixed makes the two the same space by definition. */
.mtour-ring {
  position: fixed;
  border: 2px solid #024b97;
  border-radius: 3px;
  box-shadow: 0 0 0 2px rgba(255,255,255,.92);
  pointer-events: none;
}
.mtour-card {
  position: fixed;
  left: max(10px, env(safe-area-inset-left));
  right: max(10px, env(safe-area-inset-right));
  background: #fff;
  border: 1px solid #e0dede;
  border-top: 3px solid #024b97;      /* the Rubicon signature rule, section 5 */
  border-radius: 0;
  padding: 14px 14px 12px;
}
/* The control is at the top of the screen, so the card takes the bottom, and the other way
   round. Both ends of this bar are always clear of the other. */
.mtour-card-low  { bottom: max(84px, calc(env(safe-area-inset-bottom) + 84px)); }
.mtour-card-high { top: max(84px, calc(env(safe-area-inset-top) + 84px)); }
.mtour-n {
  font: 800 9.5px/1 Raleway, system-ui, sans-serif;
  letter-spacing: .9px; text-transform: uppercase; color: #747474;
}
.mtour-card h2 {
  margin: 6px 0 4px;
  font: 600 16px/1.25 Raleway, system-ui, sans-serif; color: #333;
}
.mtour-card p {
  margin: 0 0 12px;
  font: 400 13px/1.45 "Open Sans", system-ui, sans-serif; color: #212121;
}
.mtour-actions { display: flex; align-items: center; gap: 8px; }
.mtour-actions button {
  min-height: 40px; padding: 0 14px; border-radius: 2px;
  font: 600 12px/1 Raleway, system-ui, sans-serif; cursor: pointer;
}
.mtour-skip { margin-right: auto; border: 0; background: transparent; color: #747474; }
.mtour-back { border: 1px solid #d2d2d2; background: #fff; color: #333; }
.mtour-back:disabled { opacity: .4; }
.mtour-next { border: 1px solid #024b97; background: #024b97; color: #fff; }
/* The tour owns the screen while it is up: a stray drag on the joystick underneath would walk
   the camera out from under the step that is describing it. */
body.mtour-open .mobile-joystick,
body.mtour-open .mobile-elevator { pointer-events: none; }

/* THE 360 WIPE SLIDER IS A DESKTOP CONTROL (2026-08-24). Anthony: "the slider to view the
   scan and 360 photo doesn't work on mobile so let's remove that and it looks really bad
   when holding the phone upright because as you can see, it's behind the bottom panel
   buttons." Both true: it needs a precise horizontal drag the phone gives to looking around
   instead, and in portrait it lands underneath the control bar. Hidden on the phone only —
   the desktop keeps it, and #wipeBar is untouched everywhere else. */
html[data-shell="mobile"] body[data-photo360-open] #wipeBar { display: none !important; }

/* HOW TO LEAVE A 360, SAID WHERE YOU ARE LOOKING. The joystick is the way out (it always
   was — the runtime exits on the first push) but it was hidden while a photo was open, so
   there was no way out at all. It stays up now, and this names it. */
.mobile-photo-exit {
  position: fixed;
  z-index: 36;
  left: max(8px, env(safe-area-inset-left));
  bottom: calc(var(--rb-bar-h, 64px) + 128px);
  max-width: 232px;
  padding: 7px 10px;
  background: rgba(2,75,151,.94);
  color: #fff;
  border-radius: 2px;
  font: 600 11.5px/1.3 Raleway, system-ui, sans-serif;
  pointer-events: none;
}
body:not([data-photo360-open]) .mobile-photo-exit { display: none; }

/* ══ THE STUCK ZOOM (2026-08-24) ═══════════════════════════════════════════════════════
   Anthony: "sometimes the screen will like resize and I'll be stuck at this level of zoom
   where it's not completely in frame and it persists even when I reload the page.. please
   fix that that is the worst bug right now."

   CAUSE, and it is a documented iOS rule rather than a guess: Safari AUTO-ZOOMS the whole
   page when you focus a text field whose computed font-size is under 16px, and it frequently
   does not zoom back out afterwards. Safari then remembers that zoom for the site, which is
   exactly why reloading did not clear it. The AI Copilot box was 15px — one pixel under —
   and the component search was 13px.

   IT ALSO EXPLAINS THE OTHER HALF OF WHAT HE REPORTED. Zoomed in, the visual viewport shows
   only part of the page, so the fixed top row sits above what you can see: "not completely
   in frame". The floor plan and the appearance button being sliced off at the top was this,
   not a layout fault — which is why 52px of padding papered over it instead of fixing it,
   and why that padding then read as "huge space... that is ugly".

   16px is the threshold, so 16px is the floor. Phone only; the desktop keeps its 13px. */
html[data-shell="mobile"] input,
html[data-shell="mobile"] textarea,
html[data-shell="mobile"] select,
html[data-shell="mobile"] .search input { font-size: 16px; }

/* ══ NOTHING IN THE VIEWER IS TEXT TO BE SELECTED (2026-08-24) ═════════════════════════
   Anthony, with a screenshot of iOS's Copy / Find Selection / Look Up bar sitting over the
   stairs: "notice how I accidentally highlighted and selected the up button... it's not
   [right] for a 3-D viewer. This just messes up the user experience."
   A long press on any chrome — the elevator arrow, a bar button, the mini map caption — is a
   text selection to the browser, and iOS then throws its edit menu over the scan. A 3D
   walkthrough has no text worth selecting, so the shell stops offering it, and
   -webkit-touch-callout stops the long-press menu that comes with it.
   The two places a person genuinely might select text are exempt below: the AI Copilot box
   they type into, and the component record they may want to copy a model number out of. */
html[data-shell="mobile"] body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
html[data-shell="mobile"] .scene-command textarea,
html[data-shell="mobile"] input,
html[data-shell="mobile"] textarea,
html[data-shell="mobile"] main > .doc {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}
