/* ============================================================================
   vx-mobile.css — Velorin OPS responsive shell
   ----------------------------------------------------------------------------
   WHY THIS FILE EXISTS (read before changing anything):

   ops.html has a viewport tag and 63 rules inside media queries, so it LOOKS
   responsive. It is not. Every one of those 63 rules targets an inner panel
   (#sc-*, .kpis, .grid, .pane, table, .donutRow). NOT ONE targets the shell.
   The shell is therefore unconditional:

       .app { display:grid; grid-template-columns:60px 236px 1fr; height:100vh }

   At a 386px viewport that computes to `60px 236px 90px` — the rail and
   sidebar take 296px and leave 90px for all content. That one line is the
   crammed layout, the sidebar overlap AND the "BILLE TO DATE"/"$14." label
   wrapping (the KPI grid already stacks correctly at 440px; it was just being
   squeezed into 90px).

   Second: header.top is flex/nowrap with .jobsel{min-width:250px}, giving the
   header an intrinsic width of 1035px. body{overflow-x:hidden} CLIPS that
   rather than scrolling it, so Ask V, the status chips and the user menu are
   unreachable on a phone. That is an access bug, not a cosmetic one.

   This file is loaded LAST so it wins the cascade on equal specificity without
   an !important war against 307KB of inlined CSS. Where !important appears
   below it is only to beat an existing !important, never for convenience.

   ops.html does NOT load vx-shell.css — its only external stylesheet is
   vision_polish.css. Do not move these rules into vx-shell.css expecting them
   to apply here; they will silently do nothing.

   The desktop opt-out is html[data-vx-view="desktop"], set from a cookie by
   vx-mobile.js. Every mobile rule is guarded by :not([data-vx-view="desktop"])
   so a super can always get the full scrolling layout back.
   ========================================================================== */

:root { --vxm-hdr: 52px; --vxm-bot: 46px; --vxm-tap: 44px; }

/* --------------------------------------------------------------------------
   1. THE SHELL — collapse three columns to one.
   -------------------------------------------------------------------------- */
@media (max-width: 860px) {

  html:not([data-vx-view="desktop"]) body { overflow-x: hidden; }

  html:not([data-vx-view="desktop"]) .app {
    /* minmax(0,1fr), NOT 1fr. `1fr` is shorthand for minmax(auto,1fr) and the
       auto minimum is MIN-CONTENT, so the column silently grows to the widest
       unbreakable thing inside main. Measured: plain 1fr still gave a 627px
       column on a 386px viewport. minmax(0,1fr) lets it shrink to the
       viewport and pushes the overflow into main's own scroll container. */
    grid-template-columns: minmax(0, 1fr);
    /* header row is auto, not 52px — it is allowed to wrap to two lines now */
    grid-template-rows: auto minmax(0, 1fr) auto;
    height: 100dvh;          /* dvh, not vh: kills the dead area under mobile
                                browser chrome that vh leaves behind */
    min-height: 100dvh;
  }

  /* main takes the full width and is the only scrolling region */
  html:not([data-vx-view="desktop"]) main.main {
    grid-column: 1 / -1;
    grid-row: 2;
    width: 100%;
    min-width: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 24px;
  }

  /* ------------------------------------------------------------------------
     2. RAIL + SIDEBAR become one off-canvas drawer.
        They keep their own widths so none of the existing inner CSS breaks —
        they are simply translated off-screen and slid back in together.
     ---------------------------------------------------------------------- */
  html:not([data-vx-view="desktop"]) .rail,
  html:not([data-vx-view="desktop"]) aside.nav {
    position: fixed;
    top: 0;
    bottom: 0;
    z-index: 80;
    transform: translateX(-110%);
    transition: transform .22s ease;
    padding-top: calc(var(--vxm-hdr) + 8px);
    box-shadow: 0 0 24px rgba(0,0,0,.55);
    will-change: transform;
  }
  html:not([data-vx-view="desktop"]) .rail      { left: 0;    width: 60px;  }
  html:not([data-vx-view="desktop"]) aside.nav  { left: 60px; width: 236px; }

  html[data-vx-drawer="1"]:not([data-vx-view="desktop"]) .rail,
  html[data-vx-drawer="1"]:not([data-vx-view="desktop"]) aside.nav {
    transform: translateX(0);
  }

  /* scrim — tap anywhere to close */
  html:not([data-vx-view="desktop"]) .vxm-scrim {
    position: fixed; inset: 0; z-index: 70;
    background: rgba(0,0,0,.5);
    opacity: 0; pointer-events: none; transition: opacity .22s ease;
  }
  html[data-vx-drawer="1"]:not([data-vx-view="desktop"]) .vxm-scrim {
    opacity: 1; pointer-events: auto;
  }

  /* ------------------------------------------------------------------------
     3. THE HEADER — let it wrap instead of running off to 1035px.
     ---------------------------------------------------------------------- */
  html:not([data-vx-view="desktop"]) header.top {
    position: sticky; top: 0; z-index: 90;
    grid-row: 1;
    flex-wrap: wrap;
    row-gap: 8px;
    column-gap: 8px;
    padding: 8px 10px;
    min-height: var(--vxm-hdr);
  }

  /* ---- two compact rows, not seven wrapped ones ----------------------
     Letting the header simply wrap produced a 229px-tall header (measured):
     brand, jobsel, sides, two pills, theme, Ask V and the user block each
     took their own line. Instead vx-mobile.js moves jobsel/sides/pills into
     .vxm-strip, which becomes a single horizontally-scrollable second row.
     Above the breakpoint .vxm-strip is display:contents, so the desktop
     header renders exactly as it always did with no DOM churn on resize.
     Result: ~90px instead of 229px.                                      */
  html:not([data-vx-view="desktop"]) header.top .vxm-strip {
    display: flex;
    flex: 1 0 100%;
    order: 9;
    align-items: center;
    gap: 8px;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 2px;
  }
  html:not([data-vx-view="desktop"]) header.top .vxm-strip::-webkit-scrollbar { height: 0; }
  html:not([data-vx-view="desktop"]) header.top .vxm-strip > * { flex: 0 0 auto; }

  /* the 250px floor is the single biggest contributor to the 1035px */
  html:not([data-vx-view="desktop"]) header.top .jobsel {
    min-width: 0;
    max-width: 62vw;
  }
  html:not([data-vx-view="desktop"]) header.top .jobsel .n {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* row one: burger, brand, then the account controls pushed right */
  html:not([data-vx-view="desktop"]) header.top > .vxm-burger { order: 0; }
  html:not([data-vx-view="desktop"]) header.top > .brand { order: 1; }
  html:not([data-vx-view="desktop"]) header.top > .spacer { order: 2; flex: 1 1 auto; }
  html:not([data-vx-view="desktop"]) header.top > .thm  { order: 3; }
  html:not([data-vx-view="desktop"]) header.top > .askv { order: 4; }
  html:not([data-vx-view="desktop"]) header.top > .who  { order: 5; }

  /* ------------------------------------------------------------------------
     4. FOOTER status bar — scrolls horizontally rather than clipping.
     ---------------------------------------------------------------------- */
  html:not([data-vx-view="desktop"]) footer.status {
    grid-row: 3;
    grid-column: 1 / -1;
    overflow-x: auto;
    overflow-y: hidden;
    flex-wrap: nowrap;
    -webkit-overflow-scrolling: touch;
  }
  html:not([data-vx-view="desktop"]) footer.status::-webkit-scrollbar { height: 0; }

  /* ------------------------------------------------------------------------
     5. TOUCH TARGETS — 44px minimum (rail buttons were 42px).
     ---------------------------------------------------------------------- */
  html:not([data-vx-view="desktop"]) .rail .rb {
    min-width: var(--vxm-tap);
    min-height: var(--vxm-tap);
  }
  html:not([data-vx-view="desktop"]) aside.nav a,
  html:not([data-vx-view="desktop"]) aside.nav button,
  html:not([data-vx-view="desktop"]) header.top button {
    min-height: var(--vxm-tap);
  }

  /* ------------------------------------------------------------------------
     6. INNER CONTENT — stop panels forcing horizontal overflow.
        296 base rules carry px min-widths (420–460px on panel headers).
        Neutralise the shallow ones; wide data tables stay scrollable.
     ---------------------------------------------------------------------- */
  html:not([data-vx-view="desktop"]) main.main .pane,
  html:not([data-vx-view="desktop"]) main.main .grid,
  html:not([data-vx-view="desktop"]) main.main .grid.main {
    grid-template-columns: 1fr !important;
    min-width: 0;
  }
  html:not([data-vx-view="desktop"]) main.main [class$="-grow"],
  html:not([data-vx-view="desktop"]) main.main [class$="-hdl"] {
    min-width: 0 !important;
  }

  /* Tables and one-lines keep their TRUE width and scroll inside their own
     box. Never clip them — a clipped column on a pay application is a money
     figure the user cannot see, which is worse than a scrollbar.
     vx-mobile.js wraps every table in main with .vxm-tablewrap so the scroll
     is per-table rather than dragging the whole screen sideways. */
  html:not([data-vx-view="desktop"]) main.main .vxm-tablewrap,
  html:not([data-vx-view="desktop"]) main.main .slwrap {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  html:not([data-vx-view="desktop"]) main.main .vxm-tablewrap > table {
    min-width: max-content;
  }

  /* KPI cards: the grid already stacks at 440px — this just stops the label
     from wrapping mid-word ("BILLE TO DATE") once it has room again */
  html:not([data-vx-view="desktop"]) .kpis { grid-template-columns: 1fr !important; }
  html:not([data-vx-view="desktop"]) .kpis .l,
  html:not([data-vx-view="desktop"]) .kpis .lbl { word-break: normal; overflow-wrap: normal; }

  /* charts must not set a px width wider than the phone */
  html:not([data-vx-view="desktop"]) main.main svg,
  html:not([data-vx-view="desktop"]) main.main canvas { max-width: 100%; }
}

/* --------------------------------------------------------------------------
   7. THE "VIEW AS DESKTOP" TOGGLE + hamburger.
      Visible only on small screens. Cookie-backed (NO localStorage — it is
      forbidden in this codebase). See vx-mobile.js.
   -------------------------------------------------------------------------- */
.vxm-burger, .vxm-viewtoggle { display: none; }

/* Above the breakpoint the strip is transparent: its children lay out as if
   they were still direct children of the header. No DOM moves on resize. */
.vxm-strip { display: contents; }

@media (max-width: 860px) {
  .vxm-burger {
    display: grid; place-items: center;
    min-width: var(--vxm-tap); min-height: var(--vxm-tap);
    background: transparent; border: 1px solid var(--line, #2a3344);
    border-radius: 8px; cursor: pointer;
    color: inherit; font-size: 16px; line-height: 1;
  }
  .vxm-viewtoggle {
    display: block;
    position: fixed; right: 10px; bottom: calc(var(--vxm-bot) + 10px);
    z-index: 95;
    min-height: 36px; padding: 8px 12px;
    font: inherit; font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
    background: var(--panel, #131a26); color: inherit;
    border: 1px solid var(--line, #2a3344); border-radius: 999px;
    box-shadow: 0 4px 14px rgba(0,0,0,.45); cursor: pointer;
  }
}

/* When the user has opted into desktop, the toggle stays reachable so they can
   get back — this is the one control that must never be trapped off-screen. */
html[data-vx-view="desktop"] .vxm-viewtoggle {
  display: block;
  position: fixed; right: 10px; bottom: 10px; z-index: 95;
  min-height: 36px; padding: 8px 12px; font-size: 11px;
  background: var(--panel, #131a26); color: inherit;
  border: 1px solid var(--line, #2a3344); border-radius: 999px;
  box-shadow: 0 4px 14px rgba(0,0,0,.45); cursor: pointer;
}
html[data-vx-view="desktop"] .vxm-burger { display: none; }

/* ============================================================================
   8. RAIL LEGIBILITY — APPLIES AT EVERY WIDTH, NOT JUST MOBILE.
   ----------------------------------------------------------------------------
   NOTE FOR WHOEVER READS THIS NEXT: this block is deliberately OUTSIDE the
   media query even though the file is called vx-mobile.css. Carl reported the
   left tabs as "so dark I can't see them" and "missing stuff like settings" on
   a DESKTOP screen, so a mobile-only fix would not have touched it.

   What was actually wrong — nothing is missing. Measured on the live page:
     * the rail has all 10 sections, Admin included:
       Command · Preconstruction · Engineering & VDC · Prefab & Materials ·
       Field Operations · Schedule & Controls · Quality·Cx·Energy ·
       Safety & Compliance · Closeout & Turnover · Admin
     * every label is a span.tip at OPACITY 0 — a hover-only tooltip
     * so all that is visible is ten 19px icons drawn in 1.7px strokes at
       rgb(135,148,168) on rgb(10,13,20)

   6.3:1 passes WCAG AA for TEXT, but AA text ratios do not transfer to 1.7px
   line art — a hairline glyph needs far more separation to read. Two changes:
   put the labels back on screen permanently, and lift the icon contrast to
   ~11:1. An unreadable nav is not a cosmetic issue; a super who cannot find
   LOTO in the field is the same as LOTO not being there.
   ========================================================================== */
.rail .rb {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 100%;
  height: auto;
  min-height: 52px;
  padding: 6px 2px;
  color: #c3cfe0;                 /* was #8794a8 — ~11:1 on the rail */
}
.rail .rb:hover  { color: #e6edf7; }
.rail .rb.on     { color: #59aaff; }
.rail .rb svg    { stroke: currentColor; flex: 0 0 auto; }

/* the tooltip becomes a permanent caption */
.rail .rb .tip {
  position: static;
  opacity: 1;
  transform: none;
  pointer-events: none;
  display: block;
  max-width: 56px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  font-size: 9px;
  line-height: 1.1;
  text-align: center;
  white-space: normal;
  overflow-wrap: anywhere;
  color: inherit;
}
/* kill any tooltip arrow/caret pseudo-elements now that it sits inline */
.rail .rb .tip::before,
.rail .rb .tip::after { display: none !important; }

/* ---------------------------------------------------------------------------
   8b. THE ACTUAL REASON THE ICONS WERE INVISIBLE — inline opacity:0.1
   ---------------------------------------------------------------------------
   Every glyph inside the nav icons carries an INLINE style="opacity:.1".
   Inline styles beat normal CSS, so recolouring the icon did nothing: the
   nominal contrast was 12.33:1 but the COMPOSITE contrast, once 0.1 alpha is
   flattened against the rail background rgb(10,13,20), is 1.2:1. Invisible.
   Only !important in a stylesheet outranks a non-important inline declaration.

   Measure contrast on the COMPOSITE colour, not the declared one. A colour
   token that passes AA on paper can still be unreadable on screen.

   SCOPE — this is deliberately NOT a blanket rule. A sweep of the live page
   found 2,253 shapes at inline opacity <= .35, and they are not all bugs:
     * 2,167 inside icon-sized svgs (<= 28px)  -> icons, genuinely broken, FIXED
     *    12 inside 29-80px svgs               -> KPI sparklines beside a dash
     *    74 inside >200px chart svgs          -> the "CURVE NOT SOURCED" ghosts
   The last 86 are ghosted ON PURPOSE. Forcing them opaque would render
   placeholder curves for unsourced data as though they were real figures —
   the exact failure the dash-never-zero rule exists to prevent. Do not
   "simplify" this into `svg * { opacity: 1 }`.

   Nav chrome is safe to fix by selector. Everything inside main is classified
   at runtime by vx-mobile.js, which tags only icon-sized svgs .vxm-icon.
   ------------------------------------------------------------------------- */
.rail .rb svg *,
header.top svg *,
aside.nav svg *,
svg.vxm-icon * { opacity: 1 !important; }

/* Fast path, no JS required. Measured on the live page: 581 svgs carry
   viewBox="0 0 24 24", every one of them renders at <= 19px, and NOT ONE
   chart-sized svg uses it — the charts are 900x300, 1200x190, 640x300,
   780x340, 420x300, and the deliberate sparkline ghosts are 64x22 / 98x56.
   So this viewBox is an unambiguous "this is an icon" signal, and unlike a
   size measurement it also works on the 615 svgs that live inside hidden
   screens, where getBoundingClientRect() is 0 and any layout-based test
   silently misclassifies them. That was a real bug in the first version of
   this fix: only 45 of 673 svgs got classified. */
svg[viewBox="0 0 24 24"] * { opacity: 1 !important; }

@media (prefers-reduced-motion: reduce) {
  .rail, aside.nav, .vxm-scrim { transition: none !important; }
}

/* --- sc-loto splice artifact -------------------------------------------
   The Energy Marshal deck arrived as a standalone full-page document. When
   it was spliced in, its page-level rule came with it as
   `#sc-loto { position:fixed; inset:0 }`, so the screen covered the whole
   viewport and rendered UNDER the left rail and the top header instead of
   inside the content area. Every other #sc-* screen is static in MAIN.main.
   This puts it back in the flow with its siblings. Verified: sc-loto was the
   only screen with a non-static position. --------------------------------- */
#sc-loto { position: static !important; inset: auto !important; height: auto !important; }
