/* ============================================================================
   GEOSTRATOS DESIGN OVERLAY - the designer's own stylesheet. LOADED LAST.
   Contract (operator ruling 2026-08-27, session 2c769967 R25 - automatic, forever):
   - This file is committed and published through UIHUB (path design/overlay.css)
     by the Design lane, with no engine-side gate and no relay. Rules here win
     the cascade over the built sheet by load order - it is loaded after it.
   - Engine ships NEVER write /design/* (guarded in deploy.py) and never
     regenerate this file: it survives every release automatically.
   - The built assets/index-*.css belongs to the engine build alone. Design
     changes go HERE, not there - nothing in this file is ever erased by a ship.
   ========================================================================= */

/* (designer's space begins below) */

/* NOTE: this file has a script half - design/overlay.js, loaded body-end from
   index.html on the same contract. It carries the movement a stylesheet cannot:
   drag, clamping and remembered positions for the layers the engine's panel law
   does not bind. Read its header before adding to either file. */

/* Draggable layers get the affordance in CSS, so the cursor is right even before
   the script has bound them. */
.gs-orient,.gs-navcluster,.gs-mod-panel{cursor:grab}
.gs-orient:active,.gs-navcluster:active{cursor:grabbing}
/* Their interactive children must not inherit the grab cursor. */
.gs-orient button,.gs-orient .vc-ring-hit,.gs-orient .vc-n,
.gs-navcluster button,.gs-mod-panel button,.gs-mod-panel select,
.gs-mod-panel input,.gs-mod-panel a,.gs-mod-panel label{cursor:pointer}
.gs-orient .vc-ring-hit{cursor:grab}

/* ============================================================================
   OV-1  STATUS LOG / LOADER TEXT: NO BOX, FROM ANY SOURCE.
   ----------------------------------------------------------------------------
   History: three attempts in the built sheet failed to give the operator bare
   loader text. 2a put an opaque plate behind each line; 2b replaced it with a
   brightness-reduced backdrop; 2c removed both. He still reported boxes.
   Root cause of the persistence: the bundle injects component-scoped <style>
   elements into the DOM at runtime (e.g. the tune float builds its own <style>
   and appends it), and any injected stylesheet sits AFTER the built sheet in
   document order, so it wins at equal specificity. Rules not visible in
   assets/index-*.css were overriding mine.
   Fix: state the intent unconditionally, from the cascade-last file, with
   !important on the box-producing properties only. This beats the built sheet
   (later file), any runtime-injected <style> (normal declarations lose to
   !important regardless of order), and any inline style set for decoration.
   DELIBERATELY NOT TOUCHED: opacity, transform and transition on the lines.
   The JS owns those - it sets line.style.opacity per row from the fade curve,
   measures offsetHeight of a new line to translate the others, and zeroes
   opacity on reap. Forcing those would break the log's behaviour.
   ========================================================================= */
.gs-statuslog,
.gs-statuslog .gs-statuslog-line,
.gs-statuslog .gs-slog-txt,
.gs-statuslog .gs-slog-ts{
  background:transparent!important;
  background-image:none!important;
  backdrop-filter:none!important;
  -webkit-backdrop-filter:none!important;
  box-shadow:none!important;
  border:0!important;
  border-radius:0!important;
}
.gs-statuslog .gs-statuslog-line{padding:0!important}
.gs-statuslog .gs-slog-txt,.gs-statuslog .gs-slog-ts{padding:0!important;margin:0}

/* The panel gets its ground back ONLY in the deliberate open/hover states,
   where a panel is the intended reading surface rather than floating text. */
.gs-statuslog.gs-slog-open,
.gs-statuslog.gs-drawer-hot{
  background:linear-gradient(180deg,#101825f2,#0a1018f7)!important;
  border:1px solid rgba(150,180,220,.38)!important;
  border-right:0!important;
  border-bottom:0!important;
  border-radius:6px 0 0 0!important;
  backdrop-filter:blur(10px) saturate(1.2)!important;
  -webkit-backdrop-filter:blur(10px) saturate(1.2)!important;
}

/* Legibility with no box: a halo carried by the glyphs themselves. Survives the
   black boot ground (nothing to render) and bright imagery alike. */
.gs-statuslog .gs-statuslog-line,
.gs-statuslog .gs-slog-txt,
.gs-statuslog .gs-slog-ts{
  text-shadow:0 1px 2px #000,0 0 3px #000,0 0 7px rgba(0,0,0,.8)!important;
}
.gs-slog-txt{color:#dce4f2!important}   /* KZIMP-0490: the span owns its colour */
.gs-slog-ts{color:rgba(160,180,210,.85)!important}

/* Same rule for the boot veil, in case the loader name is what is showing a
   plate: bare letterforms, no plate, no ring. */
.gs-bootveil,.gs-bootname{
  background-image:none!important;
  box-shadow:none!important;
  border:0!important;
}
.gs-bootveil{background:#000!important}
.gs-bootname{background:transparent!important;text-shadow:0 1px 2px #000,0 0 8px rgba(0,0,0,.8)!important}

/* ============================================================================
   OV-2  TOOL PANEL: SLIDE, NEVER POP.
   ----------------------------------------------------------------------------
   The tool panel is hidden by the GENERIC .gs-hidden{display:none} - it is not
   in the display:none!important list, so it was missed by layer 4 and by any
   search for !important. Layer 5 fixed it in the built sheet, but the same
   injected-stylesheet problem applies, so it is restated here unconditionally.
   Left-docked and not drag-positioned, so nothing measures it while hidden
   (unlike .gs-imgpanel - see OV-3).
   ========================================================================= */
.gs-toolpanel{
  transform:translateX(0);
  opacity:1;
  visibility:visible;
  transition:transform .34s var(--gs-ease-ui,cubic-bezier(.2,.7,.3,1)),
             opacity .34s var(--gs-ease-ui,cubic-bezier(.2,.7,.3,1)),
             visibility 0s;
}
.gs-toolpanel.gs-hidden{
  display:block!important;
  transform:translateX(-100%)!important;
  opacity:0!important;
  visibility:hidden!important;
  pointer-events:none!important;
  transition:transform .22s cubic-bezier(.55,0,.9,.35),
             opacity .22s cubic-bezier(.55,0,.9,.35),
             visibility 0s linear .22s!important;
}
/* Takeover stays a hard cut by design - do not slide it. */
.gs-mod-takeover .gs-toolpanel{transition:opacity .22s cubic-bezier(.55,0,.9,.35)!important}

/* ============================================================================
   MIGRATION FROM THE BUILT SHEET - ONE TIME, THEN NEVER AGAIN
   ----------------------------------------------------------------------------
   Engine ruling received via the operator: assets/index-BHO8hYMW.css is a build
   artifact and every --channel stable ship regenerates it from engine source.
   Layers 1 and 2a are already mirrored into canonical source (D312/D313) and are
   safe there. Everything else I appended to that file - the token layer and
   consolidation, the motion-curve unification, the tool panel and the mobile
   drawer - would be erased by the next stable ship. So it is re-expressed below,
   and from here on the built sheet is READ-ONLY ENGINE OUTPUT to me.
   The duplication that exists right now (these rules living in both files until
   the next ship) is deliberate and self-clearing: the built copy dies with the
   ship, this copy is ship-proof because deploy.py refuses to write /design/*.
   ========================================================================= */

/* ---- OV-3  TOKEN LAYER + CONSOLIDATION (was built-sheet layer 3) ----------
   Two problems it solves. (1) The built sheet declares .gs-bar / .gs-menu /
   .gs-console / .gs-imgpanel / .gs-auth-card two or three times in separate
   override blocks, so which value won depended on source order nobody tracked.
   (2) There was no token layer: every colour was retyped - #cfe3ff alone dozens
   of times, and panel grounds had drifted across #0a121ef0, #0a121eeb,
   #0c1018f5, #101622fa and #0c1624f2 - five darks for one intended surface.
   Living in the overlay makes this stronger than it was: cascade-last means it
   is the final word without needing to be last inside a file someone else owns.
   -------------------------------------------------------------------------- */
:root{
  --gs-ink:#cfe3ff; --gs-ink-bright:#eaf3ff; --gs-ink-dim:#9db8d8;
  --gs-ink-dimmer:#7e8da0; --gs-ink-faint:#6f7f92;
  --gs-ink-code:#b9d2f5; --gs-link:#9ec3ff;
  --gs-panel:linear-gradient(180deg,#101825f2,#0a1018f7);
  --gs-bar:linear-gradient(180deg,#121b28,#080d14);
  --gs-field:#080d16d9;
  --gs-ctl:#28466e66; --gs-ctl-hover:#466ea099;
  --gs-line:rgba(120,160,255,.28); --gs-line-soft:rgba(120,160,255,.14);
  --gs-line-strong:rgba(150,180,220,.38);
  --gs-accent:#2f6fed;
  --gs-accent-faint:#78a0ff14; --gs-accent-tint:#78a0ff1a; --gs-accent-hover:#78a0ff24;
  --gs-accent-fill:#2f6fed38; --gs-accent-strong:#2f6fed52; --gs-accent-ring:#2f6fed30;
  --gs-accent-line:#78a0ff8c; --gs-accent-line-strong:#96bef0b3;
  --gs-warn:#ffd37a; --gs-error:#ff8c8c; --gs-ok:#7fd18a;
  --gs-r-sm:4px; --gs-r:6px; --gs-r-lg:8px; --gs-r-xl:12px;
  --gs-sh-1:0 4px 10px #0000004d;
  --gs-sh-2:0 10px 30px #00000080;
  --gs-sh-3:0 16px 46px #0009;
  --gs-sh-left:-8px 0 28px #00000066;
  --gs-sh-right:8px 0 28px #00000066;
  --gs-glass:blur(10px) saturate(1.2);
  --gs-in:.34s; --gs-out:.22s; --gs-ease-out:cubic-bezier(.55,0,.9,.35);
}
.gs-bar{background:var(--gs-bar);color:var(--gs-ink);backdrop-filter:var(--gs-glass);-webkit-backdrop-filter:var(--gs-glass)}
.gs-top{border-bottom:1px solid var(--gs-line-soft);box-shadow:0 6px 18px #00000059,inset 0 1px #ffffff0d}
.gs-status,.gs-bottom{border-top:1px solid var(--gs-line-soft);box-shadow:0 -6px 18px #0000004d,inset 0 -1px #ffffff0a}
.gs-btn,.gs-iconbtn{background:var(--gs-ctl);color:var(--gs-ink);border:1px solid var(--gs-line);border-radius:var(--gs-r-sm)}
.gs-btn:hover,.gs-iconbtn:hover{background:var(--gs-ctl-hover);border-color:var(--gs-accent-line)}
.gs-btn:active,.gs-iconbtn:active{background:var(--gs-accent-fill);transform:translateY(1px)}
.gs-bar .gs-btn:hover,.gs-bar .gs-iconbtn:hover{transform:translateY(-1px);box-shadow:var(--gs-sh-1)}
.gs-bar .gs-btn:active,.gs-bar .gs-iconbtn:active{transform:translateY(0);box-shadow:none}
.gs-bar .gs-iconbtn:not(.gs-lang-btn){width:20px;height:18px;padding:0;display:inline-flex;align-items:center;justify-content:center;line-height:1;font-size:12px}
.gs-bar .gs-btn,.gs-bar .gs-iconbtn{height:18px;box-sizing:border-box}
.gs-bar .gs-btn{display:inline-flex;align-items:center;line-height:1}
.gs-chip{border:1px solid var(--gs-line);color:var(--gs-ink-dim);border-radius:10px}
.gs-chip:hover{background:var(--gs-accent-hover);color:var(--gs-ink)}
.gs-chip-on{background:var(--gs-ctl-hover);color:var(--gs-ink-bright)}
.gs-warn{color:var(--gs-warn)}
.gs-error{color:var(--gs-error)}
.gs-menu,.gs-imgpanel,.gs-cfg-card,.gs-cfg-dlgcard,.gs-remap,.gs-mod-panel,.gs-drawer,.gs-toolpanel{background:var(--gs-panel);color:var(--gs-ink)}
.gs-menu,.gs-imgpanel,.gs-remap{border:1px solid var(--gs-line);box-shadow:var(--gs-sh-2);backdrop-filter:var(--gs-glass);-webkit-backdrop-filter:var(--gs-glass);border-radius:var(--gs-r-lg)}
.gs-cfg-card{border:1px solid var(--gs-line);border-radius:var(--gs-r-xl);box-shadow:var(--gs-sh-3)}
.gs-drawer{border-left:1px solid var(--gs-line);box-shadow:var(--gs-sh-left)}
.gs-toolpanel{border-right:1px solid var(--gs-line);box-shadow:var(--gs-sh-right)}
.gs-mod-panel{border:1px solid var(--gs-line-soft)}
.gs-drawer-head,.gs-tp-head,.gs-imgpanel-head,.gs-remap-head,.gs-cfg-head{padding:8px 12px;border-bottom:1px solid var(--gs-line-soft)}
.gs-imgpanel-foot,.gs-cfg-dlgact{border-top:1px solid var(--gs-line-soft)}
.gs-tp-sec{border-bottom:1px solid var(--gs-line-soft)}
.gs-menu-sep{background:var(--gs-line)}
.gs-menu-item:hover:not(:disabled),.gs-menu-action:hover,.gs-imgpanel-row:hover,.gs-cfg-row:hover{background:var(--gs-accent-hover)}
.gs-menu-active,.gs-imgpanel-on{background:var(--gs-accent-tint)}
.gs-menu-action{background:var(--gs-accent-faint);border:1px dashed var(--gs-accent-line)}
.gs-menu-item,.gs-menu-action,.gs-imgpanel-row{border-radius:var(--gs-r)}
.gs-prov-check{color:var(--gs-ok)}
.gs-console{background:var(--gs-panel);border-color:var(--gs-line);color:var(--gs-ink-code)}
.gs-console-head{border-bottom:1px solid var(--gs-line-soft)}
.gs-ver,.gs-remap-note,.gs-auth-dim{color:var(--gs-ink-dimmer)}
.gs-imgpanel-attr,.gs-imgpanel-foot,.gs-imgpanel-empty,.gs-imgpanel-divnote,.gs-cfg-empty{color:var(--gs-ink-faint)}
.gs-imgpanel-gtitle,.gs-cfg-subtitle,.gs-remap-sect{color:var(--gs-ink-dim)}
.gs-help-sec b,.gs-tp-title{color:var(--gs-ink-bright)}
.gs-field select,.gs-remap-row select,.gs-cfg-profname,.gs-cfg-profscope,.gs-auth-in{background:var(--gs-field);color:var(--gs-ink-bright);border:1px solid var(--gs-line);border-radius:var(--gs-r-sm)}
.gs-auth-in{border-radius:var(--gs-r);padding:7px 9px}
.gs-auth-in:focus{outline:none;border-color:var(--gs-accent-line);box-shadow:0 0 0 3px var(--gs-accent-ring)}
.gs-auth-card{background:var(--gs-panel);border:1px solid var(--gs-line);border-radius:var(--gs-r-xl);box-shadow:var(--gs-sh-3),inset 0 1px #ffffff0f}
.gs-auth-primary{background:var(--gs-accent-fill);border:1px solid var(--gs-accent-line);color:var(--gs-ink-bright)}
.gs-auth-primary:hover{background:var(--gs-accent-strong);border-color:var(--gs-accent-line-strong)}
.gs-auth-link{color:var(--gs-link)}
.gs-auth-err{color:var(--gs-error)}
.gs-mode-active{background:var(--gs-accent-fill);color:var(--gs-ink-bright);border-color:var(--gs-accent-line)}
.gs-sub-active{outline-color:#78a0ffa6}

/* Bar-height truth: the bars are 24px, but the takeover rule insets panels by
   var(--gs-topbar-h,40px)/var(--gs-botbar-h,26px) and nothing defined them, so
   takeover panels sat 16px low and the veils left slivers. Define once. */
:root{--gs-topbar-h:24px;--gs-botbar-h:24px}
.gs-hopveil{inset:var(--gs-topbar-h) 0 var(--gs-botbar-h)}
.gs-statuslog,.gs-console,.gs-bartab{bottom:var(--gs-botbar-h)}

/* The first :focus-visible ring in the codebase - there was not one anywhere. */
.gs-btn:focus-visible,.gs-iconbtn:focus-visible,.gs-chip:focus-visible,.gs-menu-item:focus-visible,.gs-menu-action:focus-visible,.gs-bartab:focus-visible,.gs-imgpanel-x:focus-visible,.gs-cfg-x:focus-visible,.gs-eye:focus-visible,.gs-auth-link:focus-visible,.gs-field select:focus-visible,.gs-orient .vc-face:focus-visible,.gs-orient .vc-iso:focus-visible{outline:2px solid var(--gs-accent);outline-offset:1px}
.gs-auth-in:focus-visible{outline:none}

/* The viewcube was light-mode: #f4f5f6 faces, #2b3238 text, #0b64d8 hover. */
.gs-orient .vc-face{background:#131a26eb;color:var(--gs-ink);border:1px solid var(--gs-line)}
.gs-orient .vc-face:hover{background:#1b2740f2;color:var(--gs-ink-bright)}
.gs-orient .vc-iso{background:#131a26d9;color:var(--gs-ink);border:1px solid var(--gs-line)}
.gs-orient .vc-iso:hover{color:var(--gs-ink-bright);border-color:var(--gs-accent-line)}
.gs-orient .vc-n{background:#c8503f}
.gs-orient .vc-tick{background:#a0b4d2cc}

/* ---- OV-4  MOTION LAW: ONE ASYMMETRIC CURVE PAIR (was layer 4) -----------
   Nothing appears or vanishes; leaving accelerates away and blurs, arriving
   decelerates in with no blur. Replaces the hand-picked .18s/.24s/.3s timings
   scattered through the built sheet. Feedback rides the fast token so it feels
   instant while surfaces feel considered.
   -------------------------------------------------------------------------- */
.gs-menu{transform-origin:top right;transition:opacity var(--gs-in) var(--gs-ease-ui),transform var(--gs-in) var(--gs-ease-ui),filter var(--gs-in) var(--gs-ease-ui),visibility 0s}
.gs-menu.gs-hidden{transform:translateY(-6px) scale(.985);filter:blur(2px);transition:opacity var(--gs-out) var(--gs-ease-out),transform var(--gs-out) var(--gs-ease-out),filter var(--gs-out) var(--gs-ease-out),visibility 0s linear var(--gs-out)}
.gs-drawer{transition:transform var(--gs-in) var(--gs-ease-ui),visibility 0s linear var(--gs-out)}
.gs-drawer-open{transition:transform var(--gs-in) var(--gs-ease-ui)}
.gs-console{transition:transform var(--gs-in) var(--gs-ease-ui),visibility 0s var(--gs-out),opacity var(--gs-in) var(--gs-ease-ui)}
.gs-console-open{transition:transform var(--gs-in) var(--gs-ease-ui),visibility 0s 0s,opacity var(--gs-in) var(--gs-ease-ui)}
.gs-auth{transition:opacity var(--gs-in) var(--gs-ease-ui),visibility var(--gs-in),backdrop-filter var(--gs-in) var(--gs-ease-ui)}
.gs-auth.gs-hidden{transition:opacity var(--gs-out) var(--gs-ease-out),visibility var(--gs-out),backdrop-filter var(--gs-out) var(--gs-ease-out)}
@keyframes gs-toast-in{0%{opacity:0;transform:translate(-50%,-6px)}to{opacity:1;transform:translate(-50%,0)}}
.gs-toast{animation:gs-toast-in var(--gs-in) var(--gs-ease-ui)}
.gs-btn,.gs-iconbtn,.gs-chip,.gs-menu-item,.gs-menu-action,.gs-imgpanel-row,.gs-cfg-row,.gs-tp-row{transition:background var(--gs-motion-fast) var(--gs-ease-ui),border-color var(--gs-motion-fast) var(--gs-ease-ui),color var(--gs-motion-fast) var(--gs-ease-ui),transform var(--gs-motion-fast) var(--gs-ease-ui)}

/* ---- OV-5  MOBILE DRAWER (rest of layer 5) -------------------------------
   The @media(max-width:480px) block in the built sheet replaced the drawer's
   working slide with a display swap, so only phones popped.
   -------------------------------------------------------------------------- */
@media(max-width:480px){
  .gs-drawer{display:flex!important;transform:translateX(100%)!important;visibility:hidden;pointer-events:none;transition:transform var(--gs-out) var(--gs-ease-out),visibility 0s linear var(--gs-out)}
  .gs-drawer.gs-drawer-open{transform:translateX(0)!important;visibility:visible;pointer-events:auto;transition:transform var(--gs-in) var(--gs-ease-ui)}
}

/* ---- OV-7  THE LAST POPPING PANELS FINALLY FADE ---------------------------
   Unblocked by the engine answer (DRAG-HELPER-ANSWER-20260827, from
   src/ui/panelLaw.ts, GS-018 U4): the panel law writes inline left/top only,
   plus a one-time position:absolute / right:auto / bottom:auto / margin:0 and a
   maxHeight cap; it clamps bar-to-bar, anchors rest positions to edges, reflows
   on resize, and binds pointer events on the panel head. It NEVER touches
   opacity, visibility, transform, display, pointer-events or classList, and
   show/hide is .gs-hidden class-only with nothing persisted. So a live box while
   hidden changes nothing the law reads - the geometry objection that held this
   back is answered, and this is exactly the menu liberation from layer 1.
   TWO RULES OBSERVED: never animate left/top (the law owns them) - transform
   only; and do not set left/top/right/bottom here at all.
   The overlay loads later, so an equal-specificity !important outranks the built
   sheet's display:none!important group. The overlay cannot delete a rule, only
   outrank it - hence display:flex!important rather than removing the group.
   -------------------------------------------------------------------------- */
/* REGRESSION MITIGATION 2026-08-27: .gs-cfg is REMOVED from this group.
   It is position:fixed inset:0 - a FULL-SCREEN backdrop at z-index 70 - so
   giving it display:flex while hidden leaves an invisible sheet of glass over
   the entire app. pointer-events:none should make it inert, but anything inside
   it that re-enables pointer events (or any UA/injected rule doing so) turns it
   into a click-eater: every panel looks open because the backdrop tints, and no
   close button can be reached. The operator reported exactly that on StratoLuma.
   A full-screen modal backdrop therefore keeps display:none when hidden - it
   loses its fade, which is a fair price for a UI that responds. The CARD still
   animates on the way in, and the non-fullscreen panels below keep their fade. */
.gs-cfg.gs-hidden{display:none!important}
/* RESTORED after the bisect, with the mechanism understood.
   An element with visibility:hidden CANNOT receive pointer events, and every
   rule in this group sets it - so these fades were never capable of eating a
   click. The culprit was the #sn-root band rule further down, which set
   position:fixed over the whole inter-bar area with NO visibility guard. */
.gs-imgpanel.gs-hidden,.gs-remap.gs-hidden,.gs-tunefloat.gs-hidden{
  display:flex!important;
  opacity:0!important;
  visibility:hidden!important;
  pointer-events:none!important;
  filter:blur(3px);
  transition:opacity var(--gs-out) var(--gs-ease-out),
             filter var(--gs-out) var(--gs-ease-out),
             transform var(--gs-out) var(--gs-ease-out),
             visibility 0s linear var(--gs-out);
}
.gs-imgpanel,.gs-remap,.gs-tunefloat{
  opacity:1;
  visibility:visible;
  filter:none;
  transition:opacity var(--gs-in) var(--gs-ease-ui),
             filter var(--gs-in) var(--gs-ease-ui),
             transform var(--gs-in) var(--gs-ease-ui),
             visibility 0s;
}
/* The modal backdrop fades; its card carries the arrival movement. */
.gs-cfg-card{transition:transform var(--gs-in) var(--gs-ease-ui)}
/* Floating panels lift slightly on the way out. Transform only - the law owns
   left/top, and transform is free. */
.gs-imgpanel.gs-hidden,.gs-remap.gs-hidden,.gs-tunefloat.gs-hidden{transform:translateY(-6px) scale(.99)}

/* ---- OV-8  POST-SHIP INSURANCE (settles MIGRATION-CHECKLIST-20260827) -----
   Read from the byte-computed checklist (row 12267). Its REMOVALS half is the
   half memory could not have supplied: several of those "engine rules" are my
   OWN layers 1 and 2a, mirrored into canonical source as D312/D313 - so they
   live in the engine build now and RETURN with every stable ship. The overlay
   cannot delete them, only outrank them, which is what this section does.
   Chief among them: the layer-2a opaque scrim
     .gs-statuslog:not(.gs-slog-open) .gs-statuslog-line{background:#080d14b3;
       box-shadow:0 1px 6px #00000073,inset 0 1px #ffffff0a;
       backdrop-filter:blur(6px) saturate(1.1);text-shadow:none}
   is in the engine build. Left alone it would put the plate back behind the
   loader text at the next ship - the exact defect the operator reported twice.
   OV-1 already outranks it by property, and this restates it on the engine's own
   selector so the win does not depend on reading OV-1 correctly.
   -------------------------------------------------------------------------- */
.gs-statuslog:not(.gs-slog-open) .gs-statuslog-line{
  background:transparent!important;
  background-image:none!important;
  box-shadow:none!important;
  backdrop-filter:none!important;
  -webkit-backdrop-filter:none!important;
  padding:0!important;
  border-radius:0!important;
  text-shadow:0 1px 2px #000,0 0 3px #000,0 0 7px rgba(0,0,0,.8)!important;
}
.gs-statuslog{gap:3px}

/* DECLARED CONFLICT, deliberate: the engine added .gs-remap to the
   display:none!important group to fix a real bug - a later rule outranked
   .gs-hidden, so remap was visible from boot and its close button did nothing.
   OV-7 overrides that group. This is safe because OV-7 supplies opacity:0,
   visibility:hidden and pointer-events:none, so remap is fully hidden from boot
   and the class-only toggle still works - but it IS an override of someone
   else's fix, and it must stay visible as such. If OV-7 is ever removed, the
   engine's display:none!important must be allowed to win again. */

/* ---- OV-9  TOUCH TARGETS WITHOUT MOVING ANYTHING -------------------------
   The problem: controls are 18px inside 24px bars, well under the 44px comfort
   size on coarse pointers. The obvious fix - taller bars - is the one thing not
   worth doing: it moves --gs-topbar-h / --gs-botbar-h, the top:32px/bottom:32px
   panel insets and placeTab() together, and it changes the app's whole density.
   So: expand the HIT AREA, not the control. The app already uses this exact
   trick on .gs-bartab (content:"";position:absolute;inset:-20px -6px -9px), so
   this is the house pattern rather than an invention.
   Coarse pointers only - mouse users lose nothing, and nothing visual changes at
   any pointer type. Zero layout impact: the pseudo-element is absolutely
   positioned inside an existing positioned/inline-flex control.
   -------------------------------------------------------------------------- */
@media(pointer:coarse){
  .gs-bar .gs-btn,.gs-bar .gs-iconbtn,.gs-console-head .gs-btn,.gs-console-head .gs-chip,
  .gs-imgpanel-x,.gs-cfg-x,.gs-drawer-head .gs-btn,.gs-tp-head .gs-btn,.gs-remap-head .gs-btn{
    position:relative;
  }
  .gs-bar .gs-btn:after,.gs-bar .gs-iconbtn:after,.gs-console-head .gs-btn:after,
  .gs-console-head .gs-chip:after,.gs-imgpanel-x:after,.gs-cfg-x:after,
  .gs-drawer-head .gs-btn:after,.gs-tp-head .gs-btn:after,.gs-remap-head .gs-btn:after{
    content:"";
    position:absolute;
    top:50%;
    left:50%;
    width:max(100%,44px);
    height:44px;
    transform:translate(-50%,-50%);
    /* invisible, and it must never eat the neighbour's clicks visually */
    background:transparent;
  }
  /* Menu rows are already tall enough to hit; give them the comfortable minimum
     rather than a pseudo-element, since they stack vertically and can grow. */
  .gs-menu-item,.gs-menu-action,.gs-imgpanel-row,.gs-cfg-row{min-height:36px}
}

/* ---- OV-10  THE TOP-RIGHT CORNER: ONE VIEWCUBE, NOT TWO ------------------
   Three things occupy the same corner: .gs-navcluster (top 34 / right 8, holding
   a .gs-viewcube grid plus the minimap), .gs-orient (top 54 / right 14, the
   newer ring-and-cube widget shown only when body.gs-world-live), and
   .gs-imgpanel (top 56 / right 16, law-positioned and draggable).
   The imagery panel is the law's business - it clamps bar-to-bar and the user
   drags it, so it is allowed to cover things and I leave it alone.
   The real defect is the other two: when the world is live, BOTH cube widgets
   render in the same 100px of screen, overlapping. That is not a spacing problem
   to be nudged, it is one widget too many. .gs-orient supersedes the legacy grid
   cube, so when .gs-orient is showing, the legacy cube inside the navcluster
   stands down - and the minimap, which .gs-orient does not duplicate, stays.
   CONSERVATIVE AND REVERSIBLE: nothing moves, nothing is renamed, no JS-set
   class is touched, and deleting this one rule restores the previous state
   exactly. If the legacy cube turns out to be the live one in some mode, say so
   and I will invert it instead.
   -------------------------------------------------------------------------- */
body.gs-world-live .gs-navcluster .gs-viewcube{display:none}
body.gs-world-live .gs-navcluster{gap:0}

/* ---- OV-11  MODULE BASELINE: #sn-root AND .gs-mod-* INHERIT THE HOUSE -----
   Why this exists: modules (StratoNavia et al) are mounted from their own entry
   bundles into #sn-root and .gs-mod-panel. Their markup is not in this app's
   source, so nothing here has ever styled it - which means every raw <button>,
   <select>, <input>, <table> and heading a module renders arrives with BROWSER
   DEFAULTS: light-grey chrome, blue links, 16px Times-ish headings, on a black
   12px system-ui instrument panel. That is the mess, and it is a systems
   problem rather than a per-module one.
   Scope discipline, because I cannot see the module's markup from here:
     - TYPOGRAPHY, COLOUR and CONTROL SKIN only.
     - NO layout: no display, flex, grid, position, width, height, float or
       margin on module content. If a module lays itself out badly, that is a
       conversation with the module, not something to guess at from the cascade.
     - No !important anywhere in this section: a module that deliberately styles
       its own element must keep winning. This is a floor, not a ceiling.
   -------------------------------------------------------------------------- */
#sn-root,.gs-mod-panel{font:12px/1.5 system-ui,sans-serif;color:var(--gs-ink)}
#sn-root a,.gs-mod-panel a{color:var(--gs-link)}
#sn-root a:hover,.gs-mod-panel a:hover{color:var(--gs-ink-bright)}

/* Headings: modules reach for h1-h4 and get browser sizes that dwarf a 24px bar.
   Bring them onto a scale that belongs in an instrument panel. */
#sn-root h1,.gs-mod-panel h1{font-size:16px;font-weight:600;margin:0 0 8px}
#sn-root h2,.gs-mod-panel h2{font-size:14px;font-weight:600;margin:0 0 6px}
#sn-root h3,.gs-mod-panel h3,#sn-root h4,.gs-mod-panel h4{font-size:12px;font-weight:600;margin:0 0 4px;color:var(--gs-ink-bright)}
#sn-root p,.gs-mod-panel p{margin:0 0 8px}
#sn-root small,.gs-mod-panel small{font-size:11px;color:var(--gs-ink-dimmer)}
#sn-root hr,.gs-mod-panel hr{border:0;border-top:1px solid var(--gs-line-soft);margin:10px 0}
#sn-root code,.gs-mod-panel code{font:11px/1.5 ui-monospace,monospace;color:var(--gs-ink-code)}

/* Raw controls: the single biggest source of the mess. Same skin the app's own
   .gs-btn / .gs-field select already use, so a module that renders plain HTML
   looks like it belongs without the module changing a line. */
#sn-root button,.gs-mod-panel button,
#sn-root select,.gs-mod-panel select,
#sn-root input,.gs-mod-panel input,
#sn-root textarea,.gs-mod-panel textarea{
  font:inherit;
  color:var(--gs-ink);
  background:var(--gs-ctl);
  border:1px solid var(--gs-line);
  border-radius:var(--gs-r-sm);
  padding:2px 8px;
}
#sn-root button:hover,.gs-mod-panel button:hover{background:var(--gs-ctl-hover);border-color:var(--gs-accent-line)}
#sn-root button:active,.gs-mod-panel button:active{background:var(--gs-accent-fill)}
#sn-root button:disabled,.gs-mod-panel button:disabled{opacity:.4}
#sn-root button,.gs-mod-panel button{cursor:pointer;transition:background var(--gs-motion-fast) var(--gs-ease-ui),border-color var(--gs-motion-fast) var(--gs-ease-ui)}
#sn-root input,.gs-mod-panel input,
#sn-root textarea,.gs-mod-panel textarea{background:var(--gs-field);color:var(--gs-ink-bright)}
#sn-root input:focus,.gs-mod-panel input:focus,
#sn-root textarea:focus,.gs-mod-panel textarea:focus,
#sn-root select:focus,.gs-mod-panel select:focus{outline:none;border-color:var(--gs-accent-line);box-shadow:0 0 0 3px var(--gs-accent-ring)}
#sn-root button:focus-visible,.gs-mod-panel button:focus-visible,
#sn-root select:focus-visible,.gs-mod-panel select:focus-visible,
#sn-root a:focus-visible,.gs-mod-panel a:focus-visible{outline:2px solid var(--gs-accent);outline-offset:1px}
#sn-root input[type=checkbox],.gs-mod-panel input[type=checkbox],
#sn-root input[type=radio],.gs-mod-panel input[type=radio]{accent-color:var(--gs-accent);padding:0}
#sn-root input[type=range],.gs-mod-panel input[type=range]{accent-color:var(--gs-accent);background:none;border:0;padding:0}
#sn-root label,.gs-mod-panel label{color:var(--gs-ink)}
#sn-root fieldset,.gs-mod-panel fieldset{border:1px solid var(--gs-line-soft);border-radius:var(--gs-r)}
#sn-root legend,.gs-mod-panel legend{color:var(--gs-ink-dim);font-size:11px}

/* Tables: modules show data, and a default table on black is unreadable. */
#sn-root table,.gs-mod-panel table{border-collapse:collapse;font-size:11px}
#sn-root th,.gs-mod-panel th{text-align:left;color:var(--gs-ink-dim);font-weight:600;border-bottom:1px solid var(--gs-line);padding:3px 8px 3px 0}
#sn-root td,.gs-mod-panel td{border-bottom:1px solid var(--gs-line-soft);padding:3px 8px 3px 0}
#sn-root tr:hover td,.gs-mod-panel tr:hover td{background:var(--gs-accent-hover)}

/* Scrollbars inside module panels, so they stop being bright white gutters. */
#sn-root *,.gs-mod-panel *{scrollbar-width:thin;scrollbar-color:rgba(120,160,255,.35) transparent}
#sn-root ::-webkit-scrollbar,.gs-mod-panel ::-webkit-scrollbar{width:8px;height:8px}
#sn-root ::-webkit-scrollbar-track,.gs-mod-panel ::-webkit-scrollbar-track{background:transparent}
#sn-root ::-webkit-scrollbar-thumb,.gs-mod-panel ::-webkit-scrollbar-thumb{background:rgba(120,160,255,.3);border-radius:4px}
#sn-root ::-webkit-scrollbar-thumb:hover,.gs-mod-panel ::-webkit-scrollbar-thumb:hover{background:rgba(120,160,255,.5)}

/* Placeholders and selection - two details that read as broken on a dark app and
   that a module author almost never remembers. */
#sn-root ::placeholder,.gs-mod-panel ::placeholder{color:var(--gs-ink-faint);opacity:1}
#sn-root ::selection,.gs-mod-panel ::selection{background:var(--gs-accent-fill);color:var(--gs-ink-bright)}

/* Modules render their own images and canvases; keep them from overflowing the
   panel they are given. Max-width only - no layout, no sizing decisions. */
#sn-root img,.gs-mod-panel img,#sn-root canvas,.gs-mod-panel canvas,#sn-root svg,.gs-mod-panel svg{max-width:100%}

/* The module mount itself: same considered arrival as every other surface, and
   the shell already gives #sn-root a gs-mod-in keyframe - this only supplies the
   curve tokens so it matches the rest of the app. */
#sn-root{animation-duration:var(--gs-in);animation-timing-function:var(--gs-ease-ui)}

/* One app, one register: a module's own primary action should read like the
   app's primary action, not like a second design system. Modules that mark up
   [data-primary], .primary or [type=submit] get the house accent treatment. */
#sn-root button[type=submit],.gs-mod-panel button[type=submit],
#sn-root button.primary,.gs-mod-panel button.primary,
#sn-root button[data-primary],.gs-mod-panel button[data-primary]{
  background:var(--gs-accent-fill);
  border-color:var(--gs-accent-line);
  color:var(--gs-ink-bright);
}
#sn-root button[type=submit]:hover,.gs-mod-panel button[type=submit]:hover,
#sn-root button.primary:hover,.gs-mod-panel button.primary:hover,
#sn-root button[data-primary]:hover,.gs-mod-panel button[data-primary]:hover{
  background:var(--gs-accent-strong);
  border-color:var(--gs-accent-line-strong);
}

/* ---- OV-12  THE EXITS (engine hooks landed at stable v0.41.26) ------------
   The last gap in the motion law. Pop-in was fixable from CSS; pop-OUT never was
   - the JS removes the node, and a stylesheet cannot animate an element that no
   longer exists. The engine now holds the door open:
     TOASTS:  .gs-toast-out is added to the toast before removal.
     MODULES: body.gs-mod-out is set on unmount, and the engine waits for
              animationend on #sn-root / .gs-mod-panel, capped at 400ms.
   NOTE THE MECHANISM DIFFERENCE, it matters: the module exit is awaited via
   animationend, so it must be an ANIMATION, not a transition - a transition
   would never fire the event and the engine would simply hit its 400ms cap and
   tear the node out mid-fade. Both animations are well inside the cap.
   Asymmetric per the law: leaving accelerates away and blurs (--gs-out .22s,
   --gs-ease-out), arriving decelerates in without blur.
   -------------------------------------------------------------------------- */
@keyframes gs-toast-out{
  0%{opacity:1;transform:translate(-50%,0);filter:blur(0)}
  to{opacity:0;transform:translate(-50%,-8px);filter:blur(2px)}
}
.gs-toast.gs-toast-out{
  animation:gs-toast-out var(--gs-out) var(--gs-ease-out) forwards;
  pointer-events:none;
}

@keyframes gs-mod-out{
  0%{opacity:1;transform:none;filter:blur(0)}
  to{opacity:0;transform:translateY(-6px);filter:blur(3px)}
}
body.gs-mod-out #sn-root,
body.gs-mod-out .gs-mod-panel{
  animation:gs-mod-out var(--gs-out) var(--gs-ease-out) forwards;
  pointer-events:none;
}
/* The arrival side, restated on the same curve so a module hop reads as one
   movement rather than two unrelated ones. The shell already gives #sn-root a
   gs-mod-in keyframe; this only supplies the tokens. */
#sn-root,.gs-mod-panel{animation-duration:var(--gs-in);animation-timing-function:var(--gs-ease-ui)}
body.gs-mod-out #sn-root,body.gs-mod-out .gs-mod-panel{animation-duration:var(--gs-out)}

/* One deliberate exception: a module that TAKES OVER the viewport should not
   also blur on the way out - the takeover is already a hard cut by design, and
   blurring a full-screen surface reads as a fault rather than a transition. */
body.gs-mod-out.gs-mod-takeover #sn-root,
body.gs-mod-out.gs-mod-takeover .gs-mod-panel{filter:none;animation-name:gs-mod-out-flat}
@keyframes gs-mod-out-flat{
  0%{opacity:1;transform:none}
  to{opacity:0;transform:none}
}

/* ---- OV-13  TAKEOVER MUST NOT FLATTEN THE PANEL SLOTS --------------------
   THE BUG, found from modules/stratonavia/manifest.json plus the shell's own
   panel() factory rather than from a screenshot.
   The factory builds each panel as `gs-mod-panel gs-mod-${slot}` - so a module
   asking for panels ["left","bottom"] gets .gs-mod-panel.gs-mod-left and
   .gs-mod-panel.gs-mod-bottom, whose slot rules place them as a left rail and a
   bottom strip. But the takeover rule is
     .gs-mod-takeover .gs-mod-panel{position:absolute;inset:VAR 0 VAR 0}
   at specificity 0-2-0, which outranks .gs-mod-left / .gs-mod-bottom at 0-1-0.
   So the moment a module takes over, EVERY panel it owns is forced into the same
   full-viewport box: the left rail and the bottom strip become two identical
   full-screen slabs stacked on each other, each with its own border and ground,
   over a still-visible globe (StratoNavia declares takeover.worldVisible true).
   Any module declaring more than one panel AND takeover hits this; Navia is
   simply the first to declare both.
   THE FIX: keep the takeover full-bleed for a panel with NO slot of its own -
   that is what the rule was for - and restore each slotted panel to its slot.
   Sizes are the shell's own values, not new inventions, and the bottom strip is
   measured off --gs-botbar-h instead of the stale 30px.
   SCOPE, per D22: these are the SHELL's panel frames, which persist across every
   module and which the shell positions. The layout INSIDE a panel remains the
   module's own business and is untouched here.
   -------------------------------------------------------------------------- */
body.gs-mod-takeover .gs-mod-panel.gs-mod-left{
  inset:var(--gs-topbar-h) auto var(--gs-botbar-h) 0;
  width:min(260px,80vw);
  border-left:none;
}
body.gs-mod-takeover .gs-mod-panel.gs-mod-right{
  inset:var(--gs-topbar-h) 0 var(--gs-botbar-h) auto;
  width:min(260px,80vw);
  border-right:none;
}
body.gs-mod-takeover .gs-mod-panel.gs-mod-bottom{
  inset:auto 8px calc(var(--gs-botbar-h) + 6px) 8px;
  border-radius:var(--gs-r);
}
/* A left AND bottom pair should not overlap at the corner: the rail stops above
   the strip rather than running behind it. */
body.gs-mod-takeover .gs-mod-panel.gs-mod-left:has(~ .gs-mod-bottom),
body.gs-mod-takeover .gs-mod-panel.gs-mod-right:has(~ .gs-mod-bottom){
  bottom:calc(var(--gs-botbar-h) + 6px);
}
/* Same correction outside takeover, where .gs-mod-bottom's 30px predates the
   bar-height tokens. */
.gs-mod-bottom{bottom:calc(var(--gs-botbar-h) + 6px)}

/* ---- OV-14  STRATONAVIA JOINS THE HOUSE (token remap, no layout) ----------
   Read from modules/stratonavia/entry.js. Navia is NOT unstyled - it ships a
   complete UI of its own (floating .fswin windows, tree, tabs, its own topbar)
   and, to its credit, defines DESIGN TOKENS scoped to #sn-root rather than
   hardcoding colours:
     --bg #14181c  --panel #1d2329  --panel2 #232a31  --line #2f3840
     --text #d7dde2  --dim #8b959e  --accent #38b26a  --accent2 #2d9157
     --warn #e0a044  --danger #e05548  --yellow #ffd94a
   That is why it reads as a different product: its accent is a GREEN, its greys
   are neutral-warm, and the engine's accent is blue #2f6fed. Nothing is wrong
   with its structure - only its palette is foreign.
   SO: remap the eleven tokens onto the house palette. Eleven declarations
   re-theme every window, button, tree row, tab, field and menu in the module
   without touching one layout rule - exactly the model the operator described:
   modules are graphical skins that must still say "I am part of this".
   WHY !important HERE, when D22 forbids it on module rules: Navia injects its
   stylesheet at MOUNT time, which is later in document order than this file, and
   its selector is also #sn-root - identical specificity, so it would win on
   order. !important is confined to the custom properties and to the handful of
   places where a colour is hardcoded past its own token. Structure, spacing,
   sizing and the module's rem-based UI Scale system are untouched.
   -------------------------------------------------------------------------- */
#sn-root{
  --bg:#080d14!important;
  --panel:#101825!important;
  --panel2:#16202e!important;
  --line:rgba(120,160,255,.28)!important;
  --text:#cfe3ff!important;
  --dim:#7e8da0!important;
  --accent:#2f6fed!important;
  --accent2:#78a0ff8c!important;
  --warn:#ffd37a!important;
  --danger:#ff8c8c!important;
  --yellow:#ffd37a!important;
  font-family:system-ui,sans-serif!important;
}

/* Hardcoded colours that bypass the tokens, so the remap alone cannot reach them. */
/* Accent buttons paint near-black text (#0c1410) for legibility on green; on the
   house blue that reads as a hole. */
#sn-root .btn.accent{color:#eaf3ff!important;font-weight:600}
#sn-root .btn.accent:hover{background:#4a82f2!important}
/* The module's primary CTA was its own blue (#2f7fe0) - close to the house
   accent but not it. Bring it onto the real one. */
#sn-root .btn.primary{background:#2f6fed!important;border-color:#78a0ff8c!important;color:#eaf3ff!important}
#sn-root .btn.primary:hover{background:#4a82f2!important}
/* Selection was green-tinted (#2b3d33). */
#sn-root .tree-row.selected{background:#2f6fed2e!important;border-color:#78a0ff8c!important}
#sn-root .tree-row:hover{background:#78a0ff1a!important}
#sn-root .chunk-item:hover,#sn-root .chunk-tbl tbody tr:hover{background:#78a0ff1a!important}
#sn-root .menu-item:not(.disabled):hover,#sn-root .ctx-item:not(.disabled):hover{background:#78a0ff24!important}
#sn-root .btn:hover{background:#466ea099!important;border-color:#78a0ff8c!important}
#sn-root .menu-sep{background:rgba(120,160,255,.28)!important}
#sn-root .cap-row,#sn-root .chunk-tbl th,#sn-root .chunk-tbl td{color:#7e8da0!important}
#sn-root .tree-row.type-GROUP{color:#9db8d8!important;background:#78a0ff0d!important}
#sn-root .btn.danger{border-color:#7a3b3b!important;color:#ff8c8c!important}

/* The floating windows: house panel ground and the shared glass, keeping their
   own geometry, radius rhythm and drag/resize behaviour exactly as authored. */
#sn-root .fswin{
  background:linear-gradient(180deg,#101825f2,#0a1018f7)!important;
  border:1px solid rgba(120,160,255,.28)!important;
  box-shadow:0 10px 30px #00000080!important;
  backdrop-filter:blur(10px) saturate(1.2);
  -webkit-backdrop-filter:blur(10px) saturate(1.2);
}
#sn-root .fswin-title{background:#16202ef2!important;border-bottom:1px solid rgba(120,160,255,.14)!important}
#sn-root .menu,#sn-root .ctx-menu{
  background:linear-gradient(180deg,#101825fa,#0a0e16fa)!important;
  border:1px solid rgba(120,160,255,.28)!important;
  box-shadow:0 10px 32px #0000008c!important;
}
/* Navia's own toast, so it matches the engine's. It already fades both ways. */
#sn-root #toast{
  background:linear-gradient(180deg,#101825f2,#0a1018f7)!important;
  color:#eaf3ff!important;
  border:1px solid rgba(120,160,255,.28)!important;
}
/* Navia's module topbar sits directly under the engine top bar, so it must read
   as a second tier of the same chrome rather than a competing bar. */
#sn-root #sn-topbar{
  background:linear-gradient(180deg,#121b28d9,#0a1018e6)!important;
  border-bottom:1px solid rgba(120,160,255,.14)!important;
}
#sn-root #searchBox{background:#080d16d9!important;border:1px solid rgba(120,160,255,.28)!important}
#sn-root #searchBox:focus{border-color:#78a0ff8c!important;box-shadow:0 0 0 3px #2f6fed30}

/* The module carries its own view cube, and it is light-mode for the same reason
   the engine's was: near-white faces and a #0b64d8 hover on a black app. */
#sn-root #viewcube .vc-face{background:#131a26eb!important;color:#cfe3ff!important;border:1px solid rgba(120,160,255,.28)!important}
#sn-root #viewcube .vc-face:hover{background:#1b2740f2!important;color:#eaf3ff!important}
#sn-root #viewcube .vc-iso{background:#131a26d9!important;color:#cfe3ff!important;border:1px solid rgba(120,160,255,.28)!important}
#sn-root #viewcube .vc-iso:hover{color:#eaf3ff!important;border-color:#78a0ff8c!important}

/* Focus rings, which the module has none of. */
#sn-root .btn:focus-visible,#sn-root .icon-btn:focus-visible,#sn-root .tab:focus-visible,
#sn-root .fswin-caret:focus-visible,#sn-root .fswin-x:focus-visible,
#sn-root select:focus-visible,#sn-root input:focus-visible{outline:2px solid #2f6fed;outline-offset:1px}

/* ---- OV-15  THE BAR-EXCLUSION LAW ----------------------------------------
   Operator law, 2026-08-27: "ui elements should never go under the top or bottom
   bars." The two bars are the only chrome that persists across every module, so
   they are the frame - nothing may hide beneath them.
   THE ROOT CAUSE, and it is one line rather than twelve: #sn-root is sized to the
   WHOLE VIEWPORT (the module's own CSS gives it height:100%), so every module
   window is positioned in a coordinate space that includes the strips the bars
   occupy. StratoNavia's "Position" window defaults to data-y="-104" - 104px up
   from the bottom - which was correct when Navia was a standalone app with no
   engine bottom bar, and is clipped the moment it runs inside the engine.
   Fixing the coordinate space fixes every window at once, present and future,
   without touching a single default position or the module's window manager:
   make the module mount BE the band between the bars.
   -------------------------------------------------------------------------- */
/* THE BUG I SHIPPED, AND ITS FIX.
   Making #sn-root a fixed full-band element is right while a module is mounted -
   it is what keeps every module window out from under the bars. But #sn-root
   PERSISTS IN THE DOM after a module unmounts, empty. Fixed + full-band + no
   visibility guard turned that leftover into a live invisible surface across the
   whole app: every panel underneath still painted (so everything looked open) and
   no click could reach anything. That is what broke StratoLuma.
   FIX: an empty mount is not a surface. :empty matches exactly the leftover case
   - no child nodes - so it is removed from layout and hit-testing entirely, while
   a mounted module still gets the band. */
#sn-root:empty{display:none!important}
#sn-root:not(:empty){
  position:fixed!important;
  inset:var(--gs-topbar-h) 0 var(--gs-botbar-h) 0!important;
  height:auto!important;
  margin:0!important;
}
/* Belt and braces: a mount that is present but has no module UI in it must not
   intercept anything either. The engine sets body.gs-mod-out on unmount. */
body:not(.gs-mod-takeover) #sn-root:not(:has(*)){pointer-events:none!important}
/* Same law for the engine's own floating layers. Each gets a ceiling measured
   from the band rather than from the viewport, so a long panel scrolls instead
   of running under the bottom bar. */
.gs-imgpanel,.gs-remap,.gs-tunefloat{
  max-height:calc(100vh - var(--gs-topbar-h) - var(--gs-botbar-h) - 24px);
}
.gs-cfg{padding-top:calc(var(--gs-topbar-h) + 24px)}
.gs-cfg-card{max-height:calc(100vh - var(--gs-topbar-h) - var(--gs-botbar-h) - 48px)}
.gs-auth-card{max-height:calc(100vh - var(--gs-topbar-h) - var(--gs-botbar-h) - 24px)}
.gs-drawer,.gs-toolpanel{top:var(--gs-topbar-h);bottom:var(--gs-botbar-h)}
.gs-mod-panel.gs-mod-left,.gs-mod-panel.gs-mod-right{top:var(--gs-topbar-h);bottom:var(--gs-botbar-h)}
/* The module's own windows inherit the band from #sn-root, but cap their body so
   a tall window scrolls internally instead of overflowing the band. */
#sn-root .fswin{max-height:calc(100% - 8px)}
#sn-root .fswin-body{max-height:none}
#sn-root .fswin:not(.sized) .fswin-body{max-height:calc(100vh - var(--gs-topbar-h) - var(--gs-botbar-h) - 88px)}

/* ---- OV-16  THINGS THAT WERE SIMPLY WRONG --------------------------------
   Free-rein pass over defects visible in the module and the shell.
   -------------------------------------------------------------------------- */
/* Navia's source rows force the label to exactly 30% of the row, so "Geo zones"
   breaks mid-phrase into two lines while a select sits half-empty beside it.
   Let the label take the width it needs and give the control the remainder. */
#sn-root .src-row>span{flex:0 1 auto!important;min-width:30%;white-space:nowrap}
#sn-root .src-row select{flex:1 1 auto!important;min-width:0}
#sn-root .src-row{gap:8px!important}
/* Its checkbox-plus-label cell was inheriting the same squeeze. */
#sn-root .src-row>span>input[type=checkbox]{flex:none;margin-right:4px}
/* The terrain indicator is a full-width warning line, not a squeezed cell. */
#sn-root .terrain-ind{white-space:normal;line-height:1.45}
/* Long file and layer names in the module were ellipsising at arbitrary widths. */
#sn-root .lr-status{max-width:none!important}

/* The engine's own leftovers, spotted while reading the shipped sheet: */
/* .gs-bartab is declared twice, the second time killing its own pointer events
   outside the open/hot states - harmless but it means the tab is inert until a
   zone class lands. Restore the intent explicitly. */
.gs-bartabzone.gs-zone-open .gs-bartab,.gs-bartabzone.gs-zone-hot .gs-bartab{pointer-events:auto}
/* .gs-help-btn fades in on hover but has no ground of its own, so it appears as
   floating glyph over the globe. */
.gs-help-btn{background:linear-gradient(180deg,#101825f2,#0a1018f7);border:1px solid rgba(120,160,255,.28);box-shadow:0 10px 30px #00000080}
/* .gs-provicon-img forces a white tile behind provider logos - correct for a
   light logo, a bright hole for everything else. Soften to the panel ground. */
.gs-provicon-img{background:#e8eef6}

/* ---- OV-17  THE SECOND TIER: NAVIA'S TOPBAR JOINS THE CHROME --------------
   Only the engine's two bars persist across modules, so a module header sitting
   directly beneath the engine top bar has to read as a SECOND TIER of the same
   chrome, not as a competing bar. Navia's #sn-topbar was built for a standalone
   app: min-height 30px, and every control carrying its 40px touch floor, which
   next to a 24px engine bar reads as a form rather than as chrome.
   The 40px floor is right on touch and wrong with a mouse, so the tightening is
   scoped to pointer:fine only - coarse pointers keep every target at 40px.
   Nothing structural: no flex, no order, no widths beyond the search field's
   own 17rem, which was sized for a full-width standalone window.
   -------------------------------------------------------------------------- */
@media(pointer:fine){
  #sn-root #sn-topbar{min-height:28px!important;padding:0 8px!important;gap:8px!important}
  #sn-root #sn-topbar .btn{min-height:22px!important;padding:2px 10px!important}
  #sn-root #sn-topbar .icon-btn{min-width:22px!important;min-height:22px!important}
  #sn-root #searchBox{min-height:22px!important;width:14rem!important;padding:2px 8px!important}
  /* The window title bars carry the same 40px floor; a title bar is chrome. */
  #sn-root .fswin-title{min-height:26px!important}
  #sn-root .fswin-caret,#sn-root .fswin-x{min-width:26px!important;min-height:26px!important}
  /* Rows inside panels: 40px per row makes a six-field form scroll for no reason.
     28px still clears comfortable mouse targeting. */
  #sn-root .field,#sn-root .src-row,#sn-root .radio-row,#sn-root .tree-row,
  #sn-root .ov-row,#sn-root .menu-item,#sn-root .ctx-item,#sn-root .tab,
  #sn-root .scale-row,#sn-root .pane-hdr{min-height:28px!important}
  #sn-root .field input,#sn-root .field select,#sn-root .src-row select{min-height:24px!important}
  #sn-root .btn.small{min-height:22px!important}
  #sn-root .btn{min-height:24px!important}
  #sn-root .btn.primary{min-height:28px!important}
}
/* The module title reads as the app's own brandmark line: same weight and
   tracking language as .gs-brandmark, without stealing its size. */
#sn-root .proj-name{letter-spacing:.02em}

/* ---- OV-18  THE TOP-RIGHT CORNER, PART TWO -------------------------------
   OV-10 removed the duplicate cube. What still collides is the minimap: the
   engine's orientation cluster carries cube + ring + minimap at the top right,
   and a module that puts a window there - StratoNavia's Sources defaults to 306px
   from the right edge - lands underneath it, which is exactly what the operator
   photographed: the Sources window disappearing behind the minimap with its own
   dropdown spilling out.
   The cube and ring are navigation and stay. The minimap is orientation decor
   the module does not need while it owns the screen, and it is the only piece
   wide enough to cause the overlap. It stands down for takeover modules and
   returns the moment you go back to the globe.
   One rule, reversible: delete it and the minimap comes back.
   -------------------------------------------------------------------------- */
body.gs-mod-takeover .gs-orient .gs-minimap,
body.gs-mod-takeover .gs-navcluster .gs-minimap{display:none}
/* And the cluster keeps its own lane: no module window may sit on top of the
   navigation cube, so it wins the z-order it always should have had. */
.gs-orient{z-index:40}

/* ---- OV-19  LANES: NOTHING STARTS ON TOP OF ANYTHING ELSE -----------------
   Operator law: every element should be moveable, and nothing should sit on top
   of anything else. The second half is a DEFAULTS problem and is CSS work; the
   first half needs a script and is requested separately (see the note at the
   end of this section).
   The engine's floating layers were each given a position in isolation, so they
   collide the moment two are open:
     .gs-orient        top 54  right 14   width 96   (+ iso button below)
     .gs-imgpanel      top 56  right 16   width 300  <- lands ON the cube
     .gs-remap         top 60  right 280  width 300  <- lands on the imagery panel
     .gs-statuslog     bottom right, 348 wide
     .gs-console       bottom left
   Reserve one lane for navigation and give every panel its own start position
   measured from the tokens rather than from hardcoded pixels. Anything the user
   drags afterwards is written inline by the panel law and still wins - these are
   starting points, not constraints.
   -------------------------------------------------------------------------- */
:root{
  --gs-lane-gutter:12px;
  /* the navigation cube's reserved column: 96 wide + the iso button under it */
  --gs-lane-nav-w:96px;
  --gs-lane-nav-h:132px;
  --gs-lane-right-1:calc(var(--gs-lane-gutter) + 2px);
  --gs-lane-right-2:calc(var(--gs-lane-right-1) + 300px + var(--gs-lane-gutter));
}
/* Navigation keeps the top-right corner to itself. */
.gs-orient{top:calc(var(--gs-topbar-h) + 16px);right:var(--gs-lane-right-1)}
.gs-navcluster{top:calc(var(--gs-topbar-h) + 10px);right:var(--gs-lane-right-1)}
/* The imagery panel starts BELOW the navigation lane instead of underneath it. */
.gs-imgpanel{
  top:calc(var(--gs-topbar-h) + var(--gs-lane-nav-h) + var(--gs-lane-gutter));
  right:var(--gs-lane-right-1);
}
/* Key bindings starts one lane to the left of imagery, at the same height, so
   opening both gives two side-by-side panels rather than a stack. */
.gs-remap{
  top:calc(var(--gs-topbar-h) + var(--gs-lane-nav-h) + var(--gs-lane-gutter));
  right:var(--gs-lane-right-2);
}
/* The status log owns the bottom-right; keep floating panels clear of its band
   by capping their height to stop just above it. */
.gs-imgpanel,.gs-remap{
  max-height:calc(100vh - var(--gs-topbar-h) - var(--gs-botbar-h) - var(--gs-lane-nav-h) - 172px);
}
/* Module windows: the module owns its own arrangement, but the ENGINE's reserved
   navigation lane is not the module's to occupy, so a module window that starts
   in the corner is nudged out of it rather than hidden behind it. StratoNavia's
   Sources window defaults to 306px from the right, directly under the cube. */
#sn-root #win-src{margin-top:var(--gs-lane-nav-h)}

/* NOTE ON "MOVEABLE", which CSS cannot deliver:
   drag is behaviour that exists only to be seen, so by the CLI test it is Design
   work - but it needs a few lines of script, and the overlay is a stylesheet.
   The engine already owns a panel law that does exactly this (inline left/top,
   bar-to-bar clamp, edge anchors, pointer binding on a panel head). The clean
   move is not to reimplement it: it is to bind the EXISTING law to the layers
   that lack it - .gs-orient, .gs-navcluster, .gs-console, .gs-statuslog and the
   module panels - which is one call per element.
   Requested from the engine side: a design/overlay.js loaded body-end from the
   source template, on the same permanent contract as design/overlay.css. With
   that hook I can bind the law myself, per element, without touching the build.
   Until it exists, this section makes sure nothing STARTS overlapped, which is
   most of the value with none of the risk. */

/* ============================================================================
   OV-6  STILL NOT DONE HERE, ON PURPOSE
   ----------------------------------------------------------------------------
   RESOLVED and moved to OV-7: the four popping panels. The drag-helper geometry
   objection was answered from engine source - the law writes inline left/top
   only and never reads or writes opacity/visibility/transform/display.
   STILL owed and impossible from CSS: the toast and #sn-root fade in but are
   REMOVED from the DOM, so they have no exit. That needs an out flag in JS - set
   it, wait the duration, then drop the node. .gs-cfg-dlg does the same via
   t.remove().
   And still owed on request: the touch-target spec (18px controls in 24px bars;
   moving it means --gs-topbar-h/--gs-botbar-h, the 32px panel insets and
   placeTab() together).
   ========================================================================= */

/* ============================================================================
   OV-9  THE THUMBS-UP GESTURE AND ITS RECORD WINDOW  (NAVIA route only)
   ----------------------------------------------------------------------------
   Operator request 2026-08-28: on /stratonavia, a DOUBLE press of both mouse
   buttons together spawns a thumbs-up at the pointer which floats and fades,
   and every spawn is listed in a floating window called Record.
   The gesture itself lives in the overlay's script half (index.html, body-end):
   a pointer gesture is state plus arithmetic over events, which a stylesheet
   cannot hold. What lives HERE is its looks.
   NO WIRE IS TOUCHED: .gs-thumb and .gs-recwin are new design-owned names, not
   engine classes, and nothing queries them but the overlay script. The window
   reuses .gs-hidden so it obeys the same motion law as every other panel, and
   it is registered with the overlay's drag list so it is moveable like the cube.
   ========================================================================= */
@keyframes gs-thumb-rise{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.6)}
  18%{opacity:1;transform:translate(-50%,-64%) scale(1.08)}
  100%{opacity:0;transform:translate(-50%,-160%) scale(1)}
}
.gs-thumb{
  position:fixed;z-index:319;pointer-events:none;font-size:30px;line-height:1;
  filter:drop-shadow(0 2px 6px #000a);
  animation:gs-thumb-rise .7s var(--gs-ease-ui,cubic-bezier(.2,.7,.3,1)) forwards;
}
.gs-recwin{
  position:fixed;z-index:317;display:flex;flex-direction:column;width:232px;
  left:16px;top:calc(var(--gs-topbar-h,24px) + 12px);
  max-height:calc(100vh - var(--gs-topbar-h,24px) - var(--gs-botbar-h,24px) - 32px);
  background:var(--gs-panel,linear-gradient(180deg,#101825f2,#0a1018f7));
  color:var(--gs-ink,#cfe3ff);
  border:1px solid var(--gs-line,rgba(120,160,255,.28));
  border-radius:var(--gs-r-lg,8px);
  box-shadow:var(--gs-sh-2,0 10px 30px #00000080);
  backdrop-filter:var(--gs-glass,blur(10px) saturate(1.2));
  -webkit-backdrop-filter:var(--gs-glass,blur(10px) saturate(1.2));
  font:12px/1.4 system-ui,sans-serif;overflow:hidden;
  opacity:1;visibility:visible;
  transition:opacity var(--gs-in,.34s) var(--gs-ease-ui,cubic-bezier(.2,.7,.3,1)),
             transform var(--gs-in,.34s) var(--gs-ease-ui,cubic-bezier(.2,.7,.3,1)),
             visibility 0s;
}
.gs-recwin.gs-hidden{
  display:flex!important;opacity:0;visibility:hidden;pointer-events:none;
  transform:translateY(-6px) scale(.985);
  transition:opacity var(--gs-out,.22s) var(--gs-ease-out,cubic-bezier(.55,0,.9,.35)),
             transform var(--gs-out,.22s) var(--gs-ease-out,cubic-bezier(.55,0,.9,.35)),
             visibility 0s linear var(--gs-out,.22s);
}
.gs-recwin-head{
  display:flex;align-items:center;gap:8px;padding:6px 8px 6px 10px;cursor:grab;
  border-bottom:1px solid var(--gs-line-soft,rgba(120,160,255,.14));
}
.gs-recwin-head:active{cursor:grabbing}
.gs-recwin-title{letter-spacing:.04em;color:var(--gs-ink-bright,#eaf3ff)}
.gs-recwin-count{margin-left:auto;color:var(--gs-ink-faint,#6f7f92);font:11px/1 ui-monospace,monospace}
.gs-recwin-x{
  background:var(--gs-ctl,#28466e66);color:var(--gs-ink,#cfe3ff);
  border:1px solid var(--gs-line,rgba(120,160,255,.28));
  border-radius:var(--gs-r-sm,4px);width:18px;height:18px;padding:0;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;font-size:11px;line-height:1;
}
.gs-recwin-x:hover{background:var(--gs-ctl-hover,#466ea099);border-color:var(--gs-accent-line,#78a0ff8c)}
.gs-recwin-body{overflow:auto;padding:4px}
.gs-recwin-row{display:flex;gap:8px;padding:3px 6px;border-radius:var(--gs-r,6px);font:11px/1.5 ui-monospace,monospace}
.gs-recwin-row:nth-child(odd){background:#78a0ff0f}
.gs-recwin-n{color:var(--gs-ink-faint,#6f7f92)}
.gs-recwin-ts{color:var(--gs-ink-dim,#9db8d8)}
.gs-recwin-at{margin-left:auto;color:var(--gs-ink-faint,#6f7f92)}
.gs-recwin-empty{padding:10px;color:var(--gs-ink-faint,#6f7f92)}
