/* Kareem TEQ Tool - Web theme.
   Design language: "Precision Slate - Layered Depth" (premium enterprise
   engineering software). Solid, opaque slate surfaces stepped by real
   brightness for legible hierarchy; a Deep Teal brand; calm enterprise
   status colors. Depth comes from LAYERED ELEVATION SHADOWS + a top-edge
   highlight on raised surfaces + recessed tactile input wells - NOT from
   glass/blur or neon glow. Every legacy selector + variable name is
   preserved; only values/effects change (structure/JS/backend untouched).

   -- PHASE 1 (token layer): this :root block + the logo SVG retint are the
      single source of truth. Later phases wire components to these tokens
      (de-glass surfaces, tactile inputs, button states, premium header,
      code-editor preview). Forbidden per brand: pure black/white, purple,
      magenta, neon RGB - none appear below. */

:root {
  /* ---- Surfaces: opaque slate, each level a distinct brightness step
     (bg < sidebar < panel2/input < panel/card < surface-4/hover). Inputs
     sit BELOW cards = recessed tactile wells. ---- */
  --bg:        #0d1622;   /* app background - dark navy (base) */
  --bg-body:   linear-gradient(165deg, #101d2b 0%, #0b131d 100%); /* subtle navy depth, not flat */
  --sidebar:   #131d27;   /* navigation rail */
  --panel2:    #192531;   /* inputs + secondary controls (recessed vs cards) */
  --panel:     #1f2d3b;   /* cards / primary raised surface */
  --surface-4: #2b3c4d;   /* hover / highest step */

  /* ---- Brand: Deep Teal (single primary; fills use dark ink for AA) ---- */
  --accent:      #16a394;   /* Deep Teal */
  --accent-hv:   #1cb8a7;   /* lighter - hover */
  --accent-press:#10897c;   /* darker - pressed */
  --accent-secondary: #4c93f5; /* Info blue - sparing secondary accent */
  --accent-dk:   rgba(22,163,148,.16);  /* active-row / badge tint */
  --accent-soft: rgba(22,163,148,.10);  /* subtle wash */
  --on-accent:   #04201c;   /* dark ink on teal fills (contrast ~5.3:1) */

  /* ---- Brand echoes (were "neon"; kept as names, repointed to brand) ---- */
  --cyan:      #16a394;                 /* -> teal */
  --cyan-soft: rgba(22,163,148,.14);
  --gold:      #17b0a0;                 /* brand eyebrow / logo secondary -> teal */
  --gold-soft: rgba(23,176,160,.14);

  /* ---- Legacy "glass" tokens, repointed to SOLID slate (no blur) so the
     old selectors that reference them render as opaque layered surfaces ---- */
  --glass:        #1f2d3b;   /* = panel */
  --glass-hover:  #2b3c4d;
  --glass-border: #324659;   /* = --border (single structural border everywhere) */

  /* ---- Text hierarchy (all AA on --bg; labels use --text-2, not --muted) ---- */
  --text:      #eaf1f7;   /* primary */
  --text-2:    #a7b6c6;   /* secondary / labels */
  --muted:     #7e8ea0;   /* tertiary (AA-passing ~5:1 on --bg) */

  /* ---- GLOBAL TYPOGRAPHY COLOR SYSTEM (single source of truth) ------------
     The enterprise text hierarchy (L1-L6 + tables). Every typographic element
     across the app references THESE tokens - never a raw hex - so the whole
     platform shares one hierarchy. See DESIGN_SYSTEM.md. ---- */
  --text-primary:     #F8FAFC;   /* L1 page titles + brand wordmark */
  --text-heading:     #E2E8F0;   /* L3 card / section titles */
  --text-value:       #F1F5F9;   /* L5 input values + table values */
  --text-secondary:   #A7B4C4;   /* L2 page descriptions + table headers */
  --text-label:       #9FB0C2;   /* L4 field labels + secondary info (audit: +brightness for small 11px labels, same family, max allowed) */
  --text-placeholder: #64748B;   /* L6 placeholders */

  /* ---- Status: calm enterprise (green / amber / red / blue) ---- */
  --ok:        #2fae6a;   --ok-hv:     #27965a;
  --warn:      #dda23c;
  --error:     #e15b52;   --error-soft: rgba(225,91,82,.12);
  --error-2:   #c27066;   /* muted/dusty red used for field-level validation (border/text) */
  --info:      #4c93f5;

  /* ---- Borders: the structural language of the whole app. ONE 1px border
     everywhere - clearly visible so a region reads instantly, never dominant.
     --border-2 is only a slightly brighter HOVER/emphasis step. ---- */
  --border:    #324659;   --border-2: #415a72;

  /* ---- Toggle switches (checkbox rendered as an ON/OFF pill, app-wide -
     see input[type=checkbox] below). Default = the same border-2/text-value
     pair every switch always used; only Light Theme repoints these. ---- */
  --switch-off:  var(--border-2);
  --switch-knob: var(--text-value);

  /* ---- Preview: a real code-editor surface (deep slate, soft readable ink) ---- */
  --preview-bg:  #0b1017;
  --preview-fg:  #d4dde7;
  --preview-accent: var(--accent);
  --preview-divider: #2A3B4D;   /* header <-> code divider (softer than --border) */

  /* ---- Per-network-group accent (Home deck) - brand-safe hues only ---- */
  --g-3g: #16a394;   /* teal */
  --g-4g: #4c93f5;   /* blue */
  --g-5g: #dda23c;   /* amber (replaces forbidden magenta) */

  /* ---- Typography ---- */
  --font: "Inter", "Segoe UI", "Tahoma", system-ui, sans-serif;
  --mono: "JetBrains Mono", "Consolas", "Courier New", monospace;
  /* Type scale + tracking (additive; applied in later phases) */
  --fs-11: 11px; --fs-12: 12px; --fs-13: 13px; --fs-14: 14px;
  --fs-16: 16px; --fs-18: 18px; --fs-20: 20px; --fs-24: 24px;
  --tracking-tight: -.01em; --tracking-wide: .04em; --tracking-caps: .08em;

  /* ---- 8-pt spacing scale ---- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;

  /* ---- Radii ---- */
  --r-sm: 6px; --r-md: 10px; --r-lg: 14px; --r-xl: 18px; --r-pill: 999px;

  /* ---- Component sizes ---- */
  --control-h: 40px;  /* standard interactive control height (buttons/chips) */

  /* ---- Elevation: layered shadows = premium depth (not flat) ---- */
  --sh-1: 0 1px 2px rgba(0,0,0,.28), 0 2px 8px rgba(0,0,0,.20);   /* resting card */
  --sh-2: 0 6px 16px rgba(0,0,0,.32), 0 2px 6px rgba(0,0,0,.24);  /* raised / hover */
  --sh-3: 0 24px 48px rgba(0,0,0,.48), 0 8px 20px rgba(0,0,0,.32);/* modal / overlay */
  --edge-hi: inset 0 1px 0 rgba(255,255,255,.05);  /* top highlight on raised surfaces */
  --sh-inset: inset 0 1px 2px rgba(0,0,0,.35);      /* recessed input well */
  --sh-md: 0 2px 8px rgba(0,0,0,.15);               /* light hover lift (between sh-1 and sh-2) */

  /* ---- Motion: professional only, 150-300ms (no infinite/decorative) ---- */
  --ease-out: cubic-bezier(.22,.61,.36,1);
  --t-fast: 150ms cubic-bezier(.4,0,.2,1);
  --t:      220ms cubic-bezier(.4,0,.2,1);
  --t-med:  250ms;
  --t-slow: 300ms cubic-bezier(.4,0,.2,1);

  /* ---- Focus ring (WCAG 2.2 - visible, 3px, teal) ---- */
  --ring: 0 0 0 3px rgba(22,163,148,.32);
  --ring-err: 0 0 0 3px rgba(225,91,82,.32);

  /* ---- Z-index scale (stacking order, low to high) ---- */
  --z-back: -1;        /* decorative background layer, behind content */
  --z-raise: 1;         /* subtle local stacking (relative-positioned wrappers) */
  --z-raise-2: 2;        /* badges/lock icons/sticky headers above --z-raise */
  --z-nav-scrim: 70;      /* mobile nav dimming layer */
  --z-nav-drawer: 80;      /* mobile nav drawer, above its scrim */
  --z-login: 1000;          /* login overlay */
  --z-modal: 1100;           /* modal dialogs, above the login overlay */
  --z-popover: 2000;          /* dropdowns/account menu, topmost */

  /* ---- Breakpoint scale (documentation/reference tokens) ----
     Standard CSS media queries cannot consume custom properties in their
     condition, so these do not drive @media directly - they exist as the
     single canonical value for every viewport tier so every @media block
     below is authored against one source of truth instead of an
     independently hand-typed magic number. Every @media in this file must
     match one of these exactly. */
  --bp-phone-max:     767px;   /* <=767px: full mobile stack */
  --bp-tablet-min:    768px;   /* tablet tier starts */
  --bp-tablet-max:    1023px;  /* tablet tier ends, .layout stacks below this */
  --bp-fluid-min:     1024px;  /* fluid clamp() range starts */
  --bp-feature-a:     1280px;  /* Router / 4G Transport column-collapse */
  --bp-laptop-max:    1365px;  /* small-laptop tier ends */
  --bp-feature-b:     1440px;  /* BB RRU Creator / 5G Active Advance column-collapse */
  --bp-fluid-max:     1919px;  /* fluid clamp() range ends */
  --bp-wide-min:      1920px;  /* .content caps at --container-max above this */
  --bp-ultrawide-min: 2560px;  /* extra side padding tier */

  /* ---- Responsive layout tokens (Page Padding / Container Width) ---- */
  --container-max:  1680px;
  --page-padding-y: clamp(16px, 6.86px + 0.893vw, 24px);
  --page-padding-x: clamp(20px, 6.29px + 1.339vw, 32px);

  /* ---- Card / Grid spacing tokens ---- */
  --card-padding: 12px 14px 14px;
  --grid-gap:     10px 12px;

  /* ---- Field label -> control gap (two confirmed values in live use) ---- */
  --field-gap:    5px;
  --field-gap-lg: 6px;
}

/* ---- Light Theme (opt-in via <html data-theme="light">, toggled from
   Settings) - "Ericsson Theme" palette (2026-08-10 update, per
   KAREEM_TEQ_Theme_3_Ericsson_Theme_COMPLETE spec, replacing the prior
   Modern Graphite Light pass at the user's explicit request): cooler
   neutral graphite surfaces with an Ericsson-inspired blue interaction
   accent (--accent itself repointed to blue for this theme only - the
   brand teal remains the DARK theme's identity and is deliberately kept
   for the logo mark below, per spec "existing Turquoise remains
   available... preserve wherever it has an existing brand role").
   Structural tokens (spacing/type/motion/z-index/breakpoints untouched). ---- */
:root[data-theme="light"] {
  --bg:        #D2D6D8;
  --bg-body:
    radial-gradient(circle at 12% 8%, rgba(11,94,145,.045), transparent 30%),
    linear-gradient(135deg, #D0D5D7 0%, #D9DDDF 52%, #D2D7D9 100%);
  --sidebar:   #CBD1D4;
  --panel2:    #CDD3D5;
  --panel:     #E4E7E8;
  --surface-4: #E7EAEB;

  /* Ericsson Theme's interaction accent is blue (spec: "Blue is an
     interaction accent... Theme 3 introduces blue as its primary
     experimental interaction accent") - unlike the prior Graphite Light
     pass, --accent itself is repointed here (scoped to this theme only;
     the Dark theme's teal --accent at :root is untouched). */
  --accent:       #0B5E91;
  --accent-hv:    #0872AE;
  --accent-press: #084D78;
  --on-accent:    #ffffff;
  --accent-secondary: #3b82f6;
  --accent-dk:    rgba(11,94,145,.14);
  --accent-soft:  rgba(11,94,145,.10);

  --glass:        #E4E7E8;
  --glass-hover:  #E7EAEB;
  --glass-border: #98A3A8;

  --switch-off:  #C2CACD;
  --switch-knob: #ffffff;

  --text:      #1F292D;
  --text-2:    #435158;
  --muted:     #4F5C62;

  --text-primary:     #1F292D;
  --text-heading:     #1F292D;
  --text-value:       #1F292D;
  --text-secondary:   #435158;
  --text-label:       #435158;
  --text-placeholder: #4F5C62;

  --ok:        #237A52;   --ok-hv:     #1C6142;
  --warn:      #9A6500;
  --error:     #B23A3A;   --error-soft: rgba(178,58,58,.14);
  --error-2:   #B23A3A;
  --info:      #2D6FA0;

  --border:    #AEB7BB;   --border-2: #98A3A8;

  /* Focus ring: Ericsson spec calls for a restrained 1px blue ring
     (section 6, "no huge aura") rather than the Dark/Graphite themes'
     3px teal ring. */
  --ring:     0 0 0 1px rgba(11,94,145,.18);
  --ring-err: 0 0 0 1px rgba(178,58,58,.28);

  /* Script preview: stays dark technical surface per spec ("Preserve
     technical dark surface. Do not convert to Light."), background
     tuned to the spec's exact preview color. */
  --preview-bg:  #17212A;
  --preview-fg:  #dce5e3;
  --preview-divider: #4A5960;

  /* Existing network semantic colors preserved as-is (spec: "Preserve
     2G/3G/4G/5G/Transport/General meaning... do not flatten them into
     the new blue"). */
  --g-4g: #2f6fd1;
  --g-5g: #a8681a;

  --sh-1: 0 3px 10px rgba(24,34,38,.07);
  --sh-2: 0 6px 18px rgba(20,30,32,.10);
  --sh-3: 0 10px 30px rgba(20,30,32,.14);
  --edge-hi: inset 0 1px 0 rgba(255,255,255,.5);
  --sh-inset: inset 0 1px 2px rgba(24,34,38,.06);
  --sh-md: 0 2px 6px rgba(24,34,38,.06);
}

/* Header pill buttons (File Server / Home / Admin dropdown trigger) use a
   white-tinted border+hover baked for the dark theme's dark header -
   invisible against Light Theme's light header. Dark-tinted equivalents
   restore visible, clearly-interactive buttons without touching the
   shared dark-theme rule. */
:root[data-theme="light"] .cmd-btn,
:root[data-theme="light"] .user-menu-trigger,
:root[data-theme="light"] .userbar-btn,
:root[data-theme="light"] .cmd-theme-toggle {
  border-color: rgba(0,0,0,.18);
}
:root[data-theme="light"] .cmd-btn:hover,
:root[data-theme="light"] .user-menu-trigger:hover,
:root[data-theme="light"] .user-menu.open .user-menu-trigger,
:root[data-theme="light"] .userbar-btn:hover,
:root[data-theme="light"] .cmd-theme-toggle:hover {
  background: rgba(0,0,0,.04);
  border-color: rgba(0,0,0,.26);
}

/* Command header in Light Theme: dedicated header tone + border per the
   approved Modern Graphite Light spec (section 11), distinct from the
   general card/panel surface color. */
:root[data-theme="light"] .cmd-header {
  background:     #C7CDD0;
  --glass-border: #AEB7BB;
  --border:       #AEB7BB;
  --border-2:     #98A3A8;
}

/* KAREEM TEQ mark (static/logo.png) is a raster baked as a near-flat
   bright cyan (~#38d0d8, sampled directly from the PNG) baked for a dark
   backdrop, with no light-theme variant - on now-light surfaces (the
   header, and both login-page logos) it has no natural edge. For the
   Ericsson Theme, recolored to match --accent (#0B5E91, the Generate
   button's color) exactly - two earlier attempts (a high-saturate() pass,
   then a hue-rotate/saturate/brightness-only pass targeting #084D78 by
   mistake) both only got within ~30 units of the actual target. This one
   is solved differently: brightness(0) saturate(100%) first flattens the
   source PNG's own color to pure black regardless of its original hue, so
   the six values after it build up an arbitrary target color from a known
   neutral base instead of trying to bend the source's existing color -
   found via numeric optimization (gradient search against the real CSS
   filter-effects matrices) against #0B5E91 specifically, not eyeballed.
   Achieved rgb(10,93,146) vs target rgb(11,94,145) - 1 unit off per
   channel, not visually distinguishable. No new image asset, no markup
   change. The stacked drop-shadow (applied after, on the now-recolored
   result) outlines its alpha silhouette (not a box/background) so the
   mark itself stays crisp on any surface - unchanged from before. */
:root[data-theme="light"] .cmd-logo,
:root[data-theme="light"] .login-hero-logo,
:root[data-theme="light"] .login-card-logo {
  filter:
    brightness(0) saturate(100%)
    invert(32%) sepia(109%) saturate(317%) hue-rotate(160deg) brightness(92%) contrast(131%)
    drop-shadow(1px 0 0 rgba(15,23,42,.30))
    drop-shadow(-1px 0 0 rgba(15,23,42,.30))
    drop-shadow(0 1px 0 rgba(15,23,42,.30))
    drop-shadow(0 -1px 0 rgba(15,23,42,.30));
}

/* Login page brand panel (.login-left) in Light Theme: NO override here on
   purpose - the base .login-left rule (linear-gradient(160deg, --sidebar,
   --bg) + the same teal radial glow) already picks up this theme's own
   --sidebar/--bg values via the CSS variables, so it renders with the
   exact same gradient formula/depth as the Dark theme automatically -
   "same idea, same depth", by construction rather than a parallel
   fancier ramp that could visually drift from it. */

/* The faint square-grid pattern (svg #lg-grid) - removed by explicit
   request, now in both themes (originally only done for Light, where it
   read as visible diagonal hatching once re-tinted dark for contrast; the
   Dark theme's own washed-out white-on-dark version got the same request
   later). Keeps only the intentional teal network/topology diagram (dots +
   connecting lines, inline SVG, untouched) as the panel's decorative
   element. */
.login-left-bg pattern path {
  stroke: transparent;
}

/* Login field labels ("Username"/"Password") read too faint on the
   Graphite Light card at the shared --text-2 tone - darkened ~20% here
   only (not the shared --text-2 token, which drives secondary text
   everywhere else in the app). */
:root[data-theme="light"] .login-card label {
  color: #37444C;
}

/* Switch/toggle track has no border at all in the base rule (relies on
   the dark theme's natural contrast against a dark backdrop) - on the
   Graphite Light surfaces the off-state track can blend into the card
   behind it, so give it a visible edge here only. */
:root[data-theme="light"] input[type=checkbox] {
  border: 1px solid var(--border-2);
}

/* Retint the inline logo SVG (index.html) from here - CSS only, no markup change.
   deckCyan: orbit-outer ring -> deep-teal sweep. deckGold: inner ring + K
   monogram -> cool platinum/steel. core-node: override the hard-coded fill
   in markup to the brand teal (CSS-only, no HTML change). */
#deckCyan stop:nth-child(1) { stop-color: #1fc2b0; }
#deckCyan stop:nth-child(2) { stop-color: #0d7d70; }
#deckGold stop:nth-child(1) { stop-color: #eaf1f7; }
#deckGold stop:nth-child(2) { stop-color: #9fb2c4; }
#deckGold stop:nth-child(3) { stop-color: #eaf1f7; }
.cmd-logo .core-node { fill: var(--accent); }

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  background: var(--bg-body);
  color: var(--text);
  font-family: var(--font);
  font-size: 0.875rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
  position: relative;
}
/* Slow-drifting mesh-gradient backdrop - a single fixed layer behind
   everything (not repeated per-element), so the "infinite depth" atmosphere
   is cheap: one blur region for the whole app, animated very slowly. */
body::before {
  content: "";
  position: fixed; inset: -15%; z-index: var(--z-back); pointer-events: none;
  background:
    radial-gradient(circle at 20% 18%, rgba(22,163,148,.06), transparent 45%),
    radial-gradient(circle at 80% 85%, rgba(22,163,148,.05), transparent 50%);
  filter: blur(90px);
  animation: mesh-drift 60s ease-in-out infinite alternate;
}

/* Global, consistent keyboard focus ring for every interactive element. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--r-sm);
}

.app { display: flex; height: 100vh; }

/* UI chrome text is not selectable -> no text (I-beam) cursor on labels,
   titles, brand, cards. Real content (inputs, preview output) stays selectable. */
.app, .login-overlay, .fs-upload-panel { -webkit-user-select: none; user-select: none; }
input, textarea, .preview-box, .preview-box * {
  -webkit-user-select: text; user-select: text; }

/* Reusable inline-icon sizing (Lucide SVGs injected by main.js). */
.ic { width: 18px; height: 18px; flex: none; stroke-width: 2; }
.ic-sm { width: 16px; height: 16px; }
.btn .ic, .topuser-link .ic, .userbar-btn .ic { width: 17px; height: 17px; }

/* ---------- Sidebar ---------- */
.rail {
  width: 252px; min-width: 252px;
  background: var(--sidebar);
  height: 100vh; overflow-y: auto;
  padding: var(--sp-2);
  border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
}
.brand { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-4) var(--sp-3) var(--sp-3);
  cursor: pointer; border-radius: var(--r-md); transition: background var(--t-fast); }
.brand:hover { background: var(--panel2); }
.topbar .tb-brand { cursor: pointer; }

/* ------------------------------------------------------------------------
   Sidebar hidden by user request (navigation is via the Home launcher).
   The markup + JS (buildNav) are kept intact - this only hides it visually.
   TO RESTORE THE SIDEBAR: delete this block.
   ------------------------------------------------------------------------ */
.rail { display: none !important; }
.topbar .hb { display: none !important; }  /* hamburger only toggled the (now hidden) sidebar */
.brand .dot { color: var(--accent); font-size: 1.625rem; line-height: 1; }
.brand .cap-1 { font-size: 0.9375rem; font-weight: 800; letter-spacing: .6px; }
.brand .cap-2 { font-size: 0.6875rem; color: var(--muted); letter-spacing: .3px; }

#nav { flex: 1 1 auto; }
.nav-group {
  background: transparent;
  border: none;
  border-radius: var(--r-lg);
  margin: var(--sp-1) var(--sp-1) var(--sp-3);
  padding: var(--sp-1) var(--sp-2) var(--sp-2);
}
.nav-group h3 {
  margin: var(--sp-2) var(--sp-2) var(--sp-2);
  font-size: 1.125rem; font-weight: 700;
  color: var(--accent);
  text-transform: uppercase; letter-spacing: .8px;
  display: flex; align-items: center; gap: var(--sp-2);
}
.nav-group h3 .grp-ic { color: var(--accent); display: inline-flex; }
.nav-group h3 .grp-ic .ic { width: 16px; height: 16px; }

.nav-item {
  position: relative;
  display: flex; align-items: center; width: 100%; text-align: left;
  background: transparent; border: none; cursor: pointer;
  color: var(--text-2); font-family: var(--font); font-size: 0.84375rem; font-weight: 500;
  padding: 9px 10px 9px 12px; border-radius: var(--r-md); margin: 2px 0;
  transition: background var(--t-fast), color var(--t-fast), transform var(--t-fast);
}
.nav-item:hover { background: var(--panel2); color: var(--text); }
.nav-item:active { transform: translateX(1px); }
.nav-item.active {
  background: var(--accent-soft); color: var(--accent); font-weight: 600;
}
/* Stronger active state: neon accent bar on the left edge. */
.nav-item.active::before {
  content: ""; position: absolute; left: 2px; top: 6px; bottom: 6px;
  width: 3px; border-radius: var(--r-pill); background: var(--accent);
}
.nav-item .chev { display: inline-flex; opacity: .55; margin-right: var(--sp-2); color: var(--muted); transition: transform var(--t-fast), opacity var(--t-fast); }
.nav-item .chev .ic { width: 15px; height: 15px; }
.nav-item:hover .chev, .nav-item.active .chev { opacity: .95; color: var(--accent); }
.nav-item.active .chev { transform: translateX(2px); }

/* ---------- Content ---------- */
/* Padding is fluid from 1024px (matches the old small-laptop tier's 16/20px
   floor) up to 1920px (lands exactly on the existing large-desktop tier's
   24/32px, see the @media(min-width:1920px) rule below - same numbers, no
   jump at the handoff) - 2026-08-04, Desktop Responsive System spec. This
   replaces the old "1366-1919 uses the base design as-is" gap (confirmed
   as the actual cause of real breakage on smaller/scaled laptops) with
   continuous scaling instead of a step. 1920+/2560+ behavior is
   deliberately unchanged (confirmed with the user - keep the 2K look). */
.content { flex: 1; height: 100vh; overflow: hidden;
  padding: var(--page-padding-y) var(--page-padding-x);
  display: flex; flex-direction: column;
  /* Desktop-first: cap the workspace width and center it so the UI never
     stretches endlessly on large monitors (whitespace, not oversized layout).
     Below the cap the app fills the viewport exactly as approved. */
  width: 100%; max-width: var(--container-max); margin-inline: auto; }
.page { flex: 1 1 auto; min-height: 0; display: none; flex-direction: column; }
.page.active { display: flex; animation: fade-up var(--t) both; }
/* ===== Global typography color system (L1-L6) — color/weight only ===== */
/* L1 Page title */
.page-title { font-size: 1.4375rem; font-weight: 700; letter-spacing: -.2px; margin: 0; color: var(--text-primary); }
/* L2 Page description */
.page-sub { color: var(--text-secondary); font-weight: 400; font-size: 0.8125rem; margin: var(--sp-1) 0 var(--sp-4); }

/* minmax() floor added 2026-08-04 (Desktop Responsive System spec): the
   workspace (form column) has priority - it can shrink to 0 fr-share
   before ever losing space to the preview panel, and the preview panel
   has a 300px floor of its own so it never gets squeezed unreadably
   narrow either. Same 3fr/2fr ratio as before at every width wide enough
   for both to fit it. */
.layout { display: grid; grid-template-columns: minmax(0, 3fr) minmax(300px, 2fr); gap: var(--sp-4); flex: 1; min-height: 0; }
/* let columns honor the 3fr/2fr ratio instead of expanding to the
   preview's long (white-space:pre) XML lines */
.layout > * { min-width: 0; }

.form { overflow-y: auto; padding-right: var(--sp-2); }

/* 5G Active internal switch (Active Configuration / DOT Deletion). Reuses
   the app's existing .segmented/.seg-btn pill control as-is (same look as
   Auto Configuration / TCU / Link Migration / Baseband RRU Creator's mode
   toggles) for consistency with the rest of KAREEM TEQ - no new button
   style introduced. Lives in #page-toolbar (see SECTIONS["5g_active"].build
   in main.js) - a non-scrolling slot above .layout, same as every other
   section's toolbar area - so this is a plain block, no sticky positioning
   or special background needed (nothing ever scrolls behind it). No
   margin-bottom of its own: #page-toolbar's own margin-bottom
   (.ip-toolbar-slot) is already the full tabs -> content gap for both tabs
   (Sectors and the upload card are each the first child of their own pane,
   neither adds another margin-top on top of it) - giving .ia-seg-wrap its
   own margin too would stack both, leaving an unnecessarily large empty
   band above the first card (Top_Section_Layout_Refinement.md). */
.ia-seg-wrap {
  margin: 0;
  padding-top: 2px;
  padding-bottom: 2px;
}
/* 5G Active's own tab-switcher, net -3% off the original (2026-08-04:
   -20%, +10%, +5%, +5%, all explicit requests, compounded) - scoped to
   .ia-seg-wrap only so the shared .segmented/.seg-btn control used by
   Auto Configuration/TCU/Link Migration/BB RRU Creator's mode toggles stays
   untouched. */
.ia-seg-wrap .segmented { padding: 3.9px; gap: 3.9px; border-radius: 9.7px; }
.ia-seg-wrap .seg-btn { padding: 7.8px 19.4px; font-size: 0.789rem; border-radius: 6.8px; }

/* Cross-fade + slide between the Active Configuration / DOT Deletion panes
   (KAREEM_TEQ_5G_Active_UX_Polish.md): both panes stay mounted at all times
   (toggled via .hidden only) - these three classes just animate the swap.
   GPU-only (opacity/transform), sequenced in JS: the outgoing pane plays
   .ia-leave, then (once display:none) the incoming pane starts at
   .ia-enter-from and is switched to .ia-enter-to on the next frame so the
   transition actually runs. Durations kept short (user feedback: the
   original 280/320ms felt slow to switch) - these MUST stay in sync with
   the matching setTimeout delays in SECTIONS["5g_active"].build(). */
.ia-pane { opacity: 1; transform: translateY(0); }
.ia-pane.hidden { display: none; }
.ia-pane.ia-leave {
  transition: opacity 120ms ease-in-out, transform 120ms ease-in-out;
  opacity: 0;
  transform: translateY(-6px);
}
.ia-pane.ia-enter-from {
  transition: none;
  opacity: 0;
  transform: translateY(6px);
}
.ia-pane.ia-enter-to {
  transition: opacity 140ms ease-in-out, transform 140ms ease-in-out;
  opacity: 1;
  transform: translateY(0);
}

/* ---------- Cards ----------
   "Carved from light": a hairline border that only glows at its diagonal
   corners (cyan -> magenta) and fades along the straight edges, via the
   classic double-background gradient-border technique (fill on padding-box,
   gradient on border-box). Only the top tier carries backdrop-filter blur -
   nested tiers reuse that same blurred backdrop and just step the fill up. */
.card {
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--panel);
  padding: var(--card-padding);
  margin-bottom: var(--sp-3);
  box-shadow: var(--sh-1);
  transition: box-shadow var(--t-fast), border-color var(--t-fast);
  /* Hardware acceleration: own GPU layer so hover repaint doesn't flicker. */
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
/* Unified Surface System: nested containers (Sector / Radio / IRU / Link)
   inherit the parent surface - NO brighter background per level (no "Russian
   doll"). Hierarchy comes from a single subtle 1px border, consistent radius,
   padding and the title divider - never from stacked background colors. */
.card .card,
.card .card .card {
  border: 1px solid var(--border);
  background: transparent;
  box-shadow: none;
}
/* L3 Card titles */
.card > .card-title {
  color: var(--text-heading); font-weight: 600; font-size: 0.8125rem;
  letter-spacing: .2px;
  margin: 0 0 8px; padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: var(--sp-2);
}
.sector-card > .card-title { font-size: 0.875rem; }

.grid { display: flex; flex-wrap: wrap; gap: var(--grid-gap); }
/* max-width fluid (2026-08-04, Desktop Responsive System spec): was a flat
   200px that 9 separate pages had to override back to none just to avoid
   fighting it (Router/4G Transport/Baseband/Link Migration/DOT Deletion's
   Antenna Group) - clamp() lets it narrow gracefully on tighter desktop
   viewports (1024-1366px) instead of needing a page-by-page override for
   the narrow end too. Those 9 existing "none" overrides are unaffected
   (they still fully remove the cap for their wider layouts). */
.field { display: flex; flex-direction: column; gap: var(--field-gap); min-width: 140px; flex: 1 1 140px; max-width: clamp(160px, 18vw, 200px); }
.field.wide { min-width: 280px; max-width: none; flex: 1 1 100%; }
/* L4 Field labels */
.field label { font-weight: 500; font-size: 0.6875rem; color: var(--text-label); letter-spacing: .2px; }
/* Optional helper text under a field: <small class="field-help"> */
.field-help { font-size: 0.6875rem; color: var(--muted); opacity: .85; margin-top: 2px; }

/* NGS Script "Port details": adjacent labels wrap to different line counts
   at this column width ("Digital Unit Number" vs "RI Port"), which pushes
   their selects out of alignment across the row. Reserve 2 lines for every
   label here so all selects in a row sit at the same height regardless. */
.ngs-ports-grid .field label { min-height: 3em; }
/* One port per row, stacked vertically, with a divider line between rows
   (2026-08-12 request: was a flex-wrap flow cramming several ports per row
   based on available width). */
.ngs-ports-grid { flex-direction: column; flex-wrap: nowrap; gap: 0; }
.ngs-port-row { display: flex; flex-wrap: wrap; gap: var(--grid-gap); padding: var(--sp-3) 0; border-bottom: 1px solid var(--border); }
.ngs-port-row:first-child { padding-top: 0; }
.ngs-port-row:last-child { border-bottom: none; padding-bottom: 0; }

/* L5 Input values */
select, input[type=text], input[type=password], input[type=date], input[type=number] {
  background: var(--panel2);
  color: var(--text-value); font-weight: 500;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 7px 10px;
  font-family: var(--font); font-size: 0.78125rem;
  width: 100%;
  outline: none;
  box-shadow: none;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
/* Open dropdown list: same dark theme, one step brighter for clear contrast.
   (Native OS popup - superseded on most systems by the .cs-list below, kept
   as a themed fallback.) */
select option { background: var(--panel); color: var(--text-value); }
select option:checked, select option:hover { background: var(--accent); color: var(--on-accent); }
.field.wide select, .field.wide input[type=text] { width: 100%; }
select:hover, input[type=text]:hover, input[type=date]:hover, input[type=number]:hover {
  border-color: var(--border-2); background: var(--panel);
}
select:focus, input[type=text]:focus, input[type=date]:focus, input[type=number]:focus { border-color: var(--accent); box-shadow: var(--ring); }
select { cursor: pointer; color-scheme: dark; }

/* Combine Cell's paste-text box (5G Active, 4th tab) - same panel/border
   language as the text inputs above, sized for multi-line pasted content
   instead of a single value. */
.cc-paste-box {
  background: var(--panel2); color: var(--text-value); font-weight: 500;
  border: 1px solid var(--border); border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
  font-family: var(--mono); font-size: 0.78125rem; line-height: 1.55;
  width: 100%; min-height: 154px; resize: vertical; outline: none; box-shadow: none;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
.cc-paste-box:hover { border-color: var(--border-2); background: var(--panel2); }
/* Light theme: paste box background darkened a bit more, same tone as the
   field boxes below it - explicit user report (too light/washed out). */
:root[data-theme="light"] .cc-paste-box,
:root[data-theme="light"] .cc-paste-box:hover { background: #C0C6C9; }
.cc-paste-box:focus { border-color: var(--accent); box-shadow: var(--ring); }
/* Analyze's own alarm, right under the paste box - plain text, no box
   (own class so clearFieldError(f) never removes it - see
   buildCombineCellPane). .err mirrors .field-err-msg's red; .ok reuses the
   same --ok green the rest of the app already uses for success. */
.cc-alert-text {
  display: block; margin-top: var(--sp-2);
  font-size: 0.6875rem; font-weight: 500; letter-spacing: .1px;
}
.cc-alert-text.err { color: var(--error-2); }
.cc-alert-text.ok { color: var(--ok); }
.cc-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); }

/* One collapsible card per recognized carrier (Analyze results). */
.cc-carrier-list { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-3); }
.cc-carrier-card { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel2); }
.cc-carrier-head { display: flex; align-items: center; gap: var(--sp-2); cursor: pointer; padding: var(--sp-2) var(--sp-3); user-select: none; }
.cc-carrier-chev { display: inline-flex; flex: none; color: var(--muted); transition: transform var(--t-fast); }
.cc-carrier-chev.cc-chev-open { transform: rotate(90deg); }
/* Title is the full MO path (no spaces in it to wrap on naturally) - break
   it across lines instead of overflowing the card. */
.cc-carrier-title { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; font-family: var(--mono); font-weight: 600; font-size: 0.8125rem; color: var(--text-heading); }
/* Body's own field rows (Cell Type / arfcn.../ AntennaUnitGroup...) get a
   flex-column gap so they read as clearly separated groups, not crowded
   together (explicit user request). */
.cc-carrier-body { padding: var(--sp-3) var(--sp-3); border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: var(--sp-3); }
/* Group label above a field row, matching the reference layout's SHAPE/
   arrangement (2026-08-21): title on the left, a thin rule continuing
   after it on the SAME line (not an underline below the whole row) -
   explicit spec: "divider must remain on the same baseline as the section
   title". Colors stay on this app's own --accent, not the reference
   image's blue (explicit user correction: shape only, not color). */
.cc-section-head {
  display: flex; align-items: center; gap: var(--sp-3);
  color: var(--accent); font-weight: 600; font-size: 0.75rem; letter-spacing: .3px;
}
.cc-section-head::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--border); }
/* Boxes/text sized down 20% from the reference-image-matched size, then
   back up 10% from that for a test look (explicit user requests,
   2026-08-21) - net ~88% of the reference-matched size. Still fits the
   full 5-field row on one line. */
.cc-row-tight { gap: 11px; flex-wrap: nowrap; }
.cc-row-tight > .field { flex: 0 0 auto; width: auto; min-width: 0; max-width: none; }
.cc-row-tight .field label { white-space: nowrap; font-size: 0.6rem; }
.cc-row-tight .bb-gen-wrap { width: auto; gap: 7px; }
.cc-row-tight .bb-gen-input,
.cc-row-tight input,
.cc-row-tight select { width: 123px; flex: none; font-size: 0.75rem; font-weight: 600; }
.cc-row-tight .bb-gen-input,
.cc-row-tight input[type=text],
.cc-row-tight select { padding: 10px 11px; border-radius: 12px; }
/* Hover feedback while a box is actually editable (unlocked via pencil) -
   explicit user request: the border should visibly color as soon as the
   mouse is over it, not just on focus. Scoped to this card, not the
   shared .bb-gen-input rule Baseband Bridge also uses. */
.cc-carrier-body .bb-gen-input:not([readonly]):hover { border-color: var(--accent); }
/* Thicker, solid (not dashed) borders in both themes - explicit repeated
   user request for clearer/more visible box edges than this app's default
   1px. */
.cc-carrier-body input,
.cc-carrier-body select,
.cc-carrier-body .bb-edit { border-width: 2px; }
.cc-carrier-body .bb-gen-input[readonly] { border-style: solid; }
/* Light theme border color: back to this app's own standard --border-2
   (matches every other page's input styling, e.g. DOT Deletion/Baseband
   Bridge) rather than a one-off override - explicit user correction: use
   "our system", not an invented color. Thicker 2px + solid (above) already
   carries the extra visibility. */
:root[data-theme="light"] .cc-carrier-body input,
:root[data-theme="light"] .cc-carrier-body select { border-color: var(--border-2); }
:root[data-theme="light"] .cc-carrier-body .bb-gen-input[readonly] {
  border-style: solid; border-color: var(--border-2);
}
/* Light theme: input background (--panel2) sat too close to the page
   background for the text cursor to read clearly against it - explicit
   user report. A visibly darker fill fixes the contrast. */
:root[data-theme="light"] .cc-carrier-body input,
:root[data-theme="light"] .cc-carrier-body select { background: #C0C6C9; }
:root[data-theme="light"] .cc-carrier-body .bb-gen-input[readonly] { background: #C6CCCF; }
/* No hover lightening on any of these boxes (dark or light theme) -
   explicit user request: stays the same dark fill on hover, doesn't
   brighten. Overrides this app's shared input/select :hover rule, which
   otherwise lightens the background app-wide. */
.cc-carrier-body input:hover,
.cc-carrier-body select:hover { background: var(--panel2); }
.cc-carrier-body .bb-gen-input[readonly]:hover { background: var(--panel2); }
:root[data-theme="light"] .cc-carrier-body input:hover,
:root[data-theme="light"] .cc-carrier-body select:hover { background: #C0C6C9; }
:root[data-theme="light"] .cc-carrier-body .bb-gen-input[readonly]:hover { background: #C6CCCF; }
/* No circular button chrome - explicit user correction: just a bare,
   smaller pencil icon, no border/circle around it. */
.cc-row-tight .bb-edit { width: 24px; height: 24px; border: none; background: transparent; }
.cc-row-tight .bb-edit .ic { width: 13px; height: 13px; }
/* ---- Themed dropdown list (replaces the OS-drawn native <select> popup, which
   ignores CSS option colours on some systems). Rendered by main.js. ---- */
.cs-list {
  position: fixed; z-index: var(--z-popover); overflow-y: auto; overscroll-behavior: contain; padding: var(--sp-1);
  background: var(--panel);                  /* elevated slate - always legible */
  border: 1px solid var(--border-2);
  border-radius: var(--r-md);
  box-shadow: var(--sh-2);
  /* Hardware acceleration: composite the whole dropdown so item hover doesn't repaint/flicker. */
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.cs-item {
  padding: 7px 12px; border-radius: var(--r-sm);
  font-family: var(--font); font-size: 0.78125rem; color: var(--text-value); font-weight: 500;
  white-space: nowrap; cursor: pointer;
}
.cs-item.sel  { background: var(--accent); color: var(--on-accent); }
/* Hover highlight = solid accent so it clearly tracks the cursor. */
.cs-item:hover { background: var(--accent); color: var(--on-accent); }
/* While the pointer is over the list, drop the persistent "selected" highlight
   on every row except the one being hovered -> the highlight follows the cursor
   instead of leaving a second, stuck highlight on the selected row. */
.cs-list:hover .cs-item.sel:not(:hover) { background: transparent; color: var(--text); }
/* L6 Placeholders (exact color, opacity 1 so it's identical across browsers) */
input::placeholder, textarea::placeholder { color: var(--text-placeholder); font-weight: 400; opacity: 1; }
/* Validation states (add .invalid / .valid on the input via JS if desired). */
input.invalid, select.invalid { border-color: var(--error); }
input.invalid:focus, select.invalid:focus { box-shadow: var(--ring-err); }
input.valid, select.valid { border-color: var(--ok); }

/* ============================================================================
   Global ON/OFF toggle switch - modern replacement for every native checkbox.
   Pure visual restyle: the native <input type=checkbox> keeps 100% of its
   checked/unchecked, focus and keyboard (Space) behavior; only its appearance
   becomes an iOS/Material pill toggle. OFF = neutral gray, ON = brand accent.
   One consistent size everywhere. Radio buttons are NOT targeted. The Router
   page's own toggle uses a hidden input (.rtr-toggle-row input) that stays
   hidden via its own later rules, so it is unaffected.
   ========================================================================== */
input[type=checkbox] {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  box-sizing: border-box; position: relative; flex: none; vertical-align: middle;
  width: 34.2px; height: 19.8px; margin: 0; padding: 0; /* -10%, explicit user request, app-wide */
  border: none; border-radius: var(--r-pill);
  background: var(--switch-off); cursor: pointer;
  transition: background 190ms var(--ease-out);
}
input[type=checkbox]::before {
  content: ""; position: absolute; top: 1.8px; left: 1.8px;
  width: 16.2px; height: 16.2px; border-radius: 50%;
  background: var(--switch-knob); box-shadow: 0 1px 2px rgba(0,0,0,.35);
  transition: transform 190ms var(--ease-out);
}
input[type=checkbox]:checked { background: var(--accent); }
input[type=checkbox]:checked::before { transform: translateX(14.4px); }
input[type=checkbox]:focus-visible { box-shadow: var(--ring); outline: none; }
input[type=checkbox]:disabled { opacity: .5; cursor: not-allowed; }

/* checkbox card */
.check-row { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) var(--sp-1); border-radius: var(--r-md); }
.check-row input[type=checkbox] { cursor: pointer; }
.check-row label { font-weight: 600; font-size: 0.9375rem; color: var(--text); cursor: pointer; }

/* ---------- Actions ---------- */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin: var(--sp-2) 0 var(--sp-6); }

/* Page header row: section title/sub on the left, action bar on the same line (right). */
.page-head { display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-4); }
.page-head-titles { min-width: 0; }
.page-head-titles .page-title, .page-head-titles .page-sub { margin: 0; }
.page-head-titles .page-sub { margin-top: var(--sp-1); }
/* gap: var(--sp-5) groups this row into 3 clusters (danger action | config
   setting | primary action) - only matters when page-actions holds more
   than one control (today: only the Activity Log toolbar below), every
   other page's single Export/Generate button is unaffected either way. */
.page-actions { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; }
.page-actions .actions { margin: 0; }   /* the mounted bar sits flush in the header */

/* Activity Log's "Auto-delete after" toolbar control - deliberately NOT
   the grid-oriented .field class (flex:1 1 140px, stacked label-over-input)
   used by Search & Filters above; that class fights a single-line button
   row (page-actions) it was never meant to sit in. This is a compact
   inline label+select instead, sized to content like its neighbors -
   tight var(--sp-3) gap keeps the label visually glued to ITS dropdown,
   while the wider gap above separates this whole cluster from Clear Log/
   Export Report on either side. */
.activity-retention-field { display: flex; align-items: center; gap: var(--sp-3); }
.activity-retention-field label { font-size: 0.75rem; color: var(--text-2); white-space: nowrap; }
/* width: auto (overriding the shared select{width:100%} rule above) lets
   the native control size itself to its widest option ("90 days (max)")
   instead of a guessed fixed px value - explicit user preference. */
.activity-retention-field select { width: auto; height: var(--control-h); }

/* "Back to Home" button on section pages (sits above the page title). */
.back-home {
  display: inline-flex; align-self: flex-start; align-items: center; gap: 6px; margin-bottom: var(--sp-3);
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-md);
  color: var(--text-2); font-family: var(--font); font-size: 0.8125rem; font-weight: 600;
  padding: 7px 13px; cursor: pointer;
  transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.back-home:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.back-home .ic { width: 16px; height: 16px; }
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  border: 1px solid transparent; border-radius: var(--r-md); cursor: pointer;
  font-family: var(--font); font-weight: 600; font-size: 0.84375rem;
  padding: 10px 16px; color: var(--text); letter-spacing: .2px;
  user-select: none; /* was flashing a text (I-beam) cursor on hover, reported live */
  transition: background var(--t-fast), border-color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
  /* Hardware acceleration: keep the lift on its own GPU layer (no repaint flicker). */
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.btn:hover { transform: translate3d(0, -1px, 0); box-shadow: var(--sh-1); }
.btn:active { transform: translate3d(0, 0, 0); box-shadow: none; }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: translate3d(0, 0, 0); box-shadow: none; }
/* Primary - electric cyan, dark ink text (bright fill needs dark text for contrast) */
.btn.accent { background: var(--accent); border-color: var(--accent); color: var(--on-accent); box-shadow: none; }
.btn.accent:hover { background: var(--accent-hv); border-color: var(--accent-hv); }
.btn.accent:active { background: var(--accent-press); border-color: var(--accent-press); }
/* Success (ZIP) - muted green, dark ink text */
.btn.ok { background: var(--ok); border-color: var(--ok); color: var(--on-accent); box-shadow: none; }
.btn.ok:hover { background: var(--ok-hv); border-color: var(--ok-hv); }
/* Secondary / ghost */
.btn.ghost { background: transparent; color: var(--text); border-color: var(--border); }
.btn.ghost:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
/* Danger - outline by default, fills solid on hover. Matches .btn-danger's
   exact color treatment (Users page delete action), unifying the two
   destructive-button variants that used to diverge (this one was solid-
   filled at rest; .btn-danger was outline-at-rest/fill-on-hover) - user-
   approved unification, 2026-07-28. Size/padding stay .btn's own (this is
   still a full modal confirm button, unlike the compact inline
   .btn-danger), only the color treatment changed. */
.btn.danger-solid { background: transparent; border-color: var(--error); color: var(--error); box-shadow: none; }
.btn.danger-solid:hover { background: var(--error); border-color: var(--error); color: var(--text); }
.status { color: var(--muted); font-size: 0.8125rem; margin-left: var(--sp-1); }
.status.ok { color: var(--ok); }
.status.err { color: var(--error); }

/* ---------- Required field validation ---------- */
.field--error input,
.field--error select { border-color: var(--error-2) !important; }
.field--error .segmented {
  border: 1px solid var(--error-2); border-radius: var(--r-md); padding: 2px;
}
.tx-field { max-width: none; flex: 1 1 auto; }
.tx-field > label { color: var(--accent); font-weight: 700; font-size: 0.75rem; }
.tx-field.field--error .port-row {
  border: 1px solid var(--error-2); border-radius: var(--r-md); padding: 4px 6px;
}
/* 5G Active only: lay the 4 transceiver checkboxes as a 2 x 2 grid
   ( 1  2  /  3  4 ) instead of a single flex row. Overrides the
   generic .port-row flex layout without affecting other pages.
   Restored 27 Jul 2026 (3rd session) - the 27 Jul density pass had changed
   this to a single row, but the user asked for Active Configuration to go
   back to matching the live server's current design exactly, so the whole
   density pass (this override + the .content[data-section="5g_active"]
   header/title/tabs/card/field sizing block that used to sit above the
   Cards section) was reverted. DOT Deletion's own AntennaUnitGroup work
   (.r1-*) is unrelated and untouched. */
.tx-field .port-row {
  /* 2026-07-28: single row of 4 instead of a 2x2 grid (explicit user
     request - supersedes the 27 Jul restore-to-2x2 note below, which is
     kept only as history of an earlier decision, not current direction). */
  display: grid; grid-template-columns: repeat(4, max-content);
  justify-content: start;
  /* Shrink-wrap to the toggle grid itself (not .tx-field's full width) so
     the error-state border (.tx-field.field--error .port-row above) hugs the
     toggles instead of stretching across the whole row. */
  width: fit-content;
  /* 2026-07-28: single bordered frame around the group (user asked to drop
     the divider-grid look but keep one shared frame) - same formula as the
     .segmented tab-switcher container (glass background, rounded frame,
     padding+gap between pills), not a rigid divided grid. */
  gap: var(--sp-1); background: var(--glass);
  border: 1px solid var(--glass-border); border-radius: var(--r-md);
  padding: var(--sp-1);
}
.field-err-msg {
  display: block; margin-top: var(--sp-1);
  font-size: 0.6875rem; font-weight: 500; color: var(--error-2); letter-spacing: .1px;
}

/* ---------- Preview ---------- */
.preview-panel {
  background: var(--glass);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-lg);
  padding: var(--sp-3) var(--sp-3) var(--sp-3);
  display: flex; flex-direction: column; min-height: 0;
  box-shadow: var(--sh-1);
}
.preview-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.preview-head .ttl { font-weight: 700; font-size: 0.75rem; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); }
.preview-head select { width: 230px; }
.preview-box {
  position: relative;
  flex: 1; min-height: 0;
  background: var(--preview-bg);
  color: var(--preview-fg);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-family: var(--mono); font-size: 0.78125rem; line-height: 1.55;
  padding: var(--sp-3); white-space: pre; overflow: auto;
  resize: none;
  box-shadow: var(--sh-inset);
}

/* segmented mode switch */
.segmented { display: inline-flex; background: var(--glass); border: 1px solid var(--glass-border); border-radius: var(--r-md); padding: var(--sp-1); gap: var(--sp-1); }
.seg-btn {
  border: none; background: transparent; color: var(--muted);
  font-family: var(--font); font-weight: 600; font-size: 0.8125rem;
  padding: 8px 20px; border-radius: 7px; cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.seg-btn:hover { color: var(--text); }
.seg-btn.active { background: var(--accent); color: var(--on-accent); box-shadow: var(--sh-2); }
.seg-hint { color: var(--muted); font-size: 0.6875rem; margin-top: var(--sp-2); }

/* RF port picker (Auto Config manual mode) - button chips, not switches
   (user-requested 2026-07-29: was a checkbox+switch per port, now each port
   is its own toggle button, filled when selected). */
.port-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; }
.port-row-lbl { color: var(--accent); font-weight: 700; font-size: 0.75rem; margin-right: 6px; }
.port-check {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--glass); border: 1px solid var(--glass-border); border-radius: var(--r-sm);
  padding: 6px 12px; font-size: 0.75rem; font-weight: 600; color: var(--muted); cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.port-check:hover { border-color: var(--accent); color: var(--text); }
.port-check:has(input:checked) { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.port-check input[type=checkbox] {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* 5G Active transceiver toggles: restyled 2026-07-28 to the same
   segmented-pill language as .seg-btn (user-approved unification of the
   three different "choose N of M" controls across 2G Baseband/5G
   Active/DOT Deletion into one style) - borderless fill pill, distinct from
   the outlined chip base .port-check now uses everywhere else. */
.tx-field .port-check {
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; color: var(--muted);
  font-family: var(--font); font-weight: 600;
  /* -10% from the previous 0.8125rem/8px 18px/7px (2026-08-04, explicit
     user request on the "Transceiver (this sector)" chips) */
  font-size: 0.73rem;
  padding: 7px 16px; border-radius: var(--r-sm); cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.tx-field .port-check:hover { color: var(--text); }
.tx-field .port-check:has(input:checked) { background: var(--accent); color: var(--on-accent); }
.tx-field .port-check input[type=checkbox] {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* 2G Baseband's TX/RX RF Branch selectors: same frame/pill look as the
   plain .segmented/.seg-btn tab-switcher (colors/padding/gap all inherited
   from those base rules - user asked to drop the earlier divider-grid
   experiment, see the removed-then-readded history in git log). Only the
   layout algorithm is scoped here: a real grid (3 columns) instead of
   flex-wrap, because flex-wrap has no way to shrink-wrap to its own
   wrapped-row width (reported by the user as an oversized box with empty
   space) - grid's fit-content sizing does this correctly, matching the
   technique already used for the 5G Active/DOT Deletion toggle frames. */
.branch-toggles {
  display: grid; grid-template-columns: repeat(3, max-content);
  width: fit-content;
}

/* placeholder page */
.placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--muted); gap: var(--sp-3); text-align: center; }
.placeholder .big { color: var(--border-2); }
.placeholder .big .ic { width: 44px; height: 44px; stroke-width: 1.5; }

/* ============================================================================
   HOME COMMAND DECK - "Nebula Launchpad"
   Full-width, zero-scroll: glass header bar + 5 open, borderless light-spaces.
   ============================================================================ */
.layout.home { grid-template-columns: 1fr; grid-template-rows: 1fr; min-height: 0; }
.layout.home .preview-panel { display: none; }
/* Users management: no preview pane -> single full-width column. */
.layout.users { grid-template-columns: 1fr; }
.layout.users .preview-panel { display: none; }
.layout.users .form { padding-right: 0; }
/* Empty-workspace pages (e.g. One Click IP, Phase 1): reuse the standard page
   shell but collapse to a single full-width area. The preview panel is hidden
   and the form column is left empty for a later phase to populate. */
.layout.empty-workspace { grid-template-columns: 1fr; }
.layout.empty-workspace .preview-panel { display: none; }
.layout.empty-workspace .form { padding-right: 0; }
/* Home fills the content height via an unbroken flex chain (robust vs height:%). */
.layout.home .form { padding-right: 0; overflow: hidden;
  display: flex; flex-direction: column; min-height: 0; }

/* On Home, let the deck fill the height under the persistent command header. */
#app-shell.home-active #page { display: flex; flex-direction: column; min-height: 0; }
#app-shell.home-active .layout.home { flex: 1 1 auto; min-height: 0; }

.home-deck {
  display: flex; flex-direction: column; gap: var(--sp-4);
  width: 100%; flex: 1 1 auto; min-height: 0; margin: 0 auto;
}

/* Shared glass surface (still used by .cmd-header). */
.glass-panel {
  position: relative; z-index: var(--z-raise);
  background: var(--glass);
  border: 1px solid var(--glass-border); border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
}

/* ---- Command header bar (persistent chrome on every page) ----
   Compacted 2026-08-04 in four passes: "Header Optimization" (~28%
   shorter), "Header Refinement Package v1.0" (precision pass, every value
   computed from the ORIGINAL pre-optimization sizes, confirmed with the
   user), "Header & User Profile Refinement (Final)" (additional ~10% off
   the header, vertical padding only), then "Header Final Compression"
   (additional ~15% target, structural padding only - logo/title/Home
   button/avatar/icons frozen exactly as-is per that spec). The last pass
   hit a real ceiling: the logo is frozen at 77px + .cmd-brand's own 8px
   vertical padding already floors the row at 85px, so .cmd-header's OWN
   padding alone could only ever buy back ~12% even at padding:0. Closed
   the rest of the gap by also trimming .cmd-brand's wrapper padding
   (structural spacing around the frozen logo, not the logo/text content
   itself) rather than stopping short of the target. Colors, icons,
   shadows, borders and hover effects are untouched throughout. */
/* Whole-header uniform -5% pass (explicit user request, 2026-08-17): every
   internal dimension below (gaps/padding/font-sizes/icon sizes/logo height/
   border-radius) multiplied by 0.95 from its prior value - colors, layout
   order, alignment and the header's own full-bleed width are untouched.
   (A second -5% pass was tried on top the same day and reverted right back
   - user's call was it looked too small - so this single -5% is final.) */
.cmd-header {
  flex: 0 0 auto; margin-bottom: var(--sp-3);
  display: flex; align-items: center; justify-content: space-between;
  gap: 11.4px; /* was var(--sp-3)=12px, -5% */ padding: 1.9px 17.1px; /* was 2px 18px, -5% */
  background: var(--panel);
  box-shadow: var(--sh-1);
}
.cmd-brand { display: flex; align-items: center; gap: 19px; /* orig 24px -15%=20px, -5% -> 19px */
  cursor: pointer; padding: 0.95px 7.6px; /* was 1px 8px, -5% */ border-radius: 9.5px; /* was var(--r-md)=10px, -5% */
  transform: translateY(-1px); /* optical centering nudge (visual only) */ }
/* No circle, no glow, no background: the logo sits directly on the header
   surface (transparent wrap) so the header color shows through. */
.cmd-logo-wrap {
  flex: none; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: none; border-radius: 0;
  box-shadow: none;
}
.cmd-logo { height: 73.15px; width: auto; object-fit: contain; display: block; } /* orig 96px, -20%=77px, -5% */
/* Orbit rings rotate slowly around the K crest (outer one way, inner the other). */
@keyframes cs-orbit { to { transform: rotate(360deg); } }
.cmd-logo .orbit-outer,
.cmd-logo .orbit-inner { transform-box: fill-box; transform-origin: center; }
.cmd-logo .orbit-outer { animation: cs-orbit 18s linear infinite; }
.cmd-logo .orbit-inner { animation: cs-orbit 26s linear infinite reverse; }

/* DOM order is sub-then-title; column-reverse puts the title on top so the
   lockup reads  KAREEM TEQ / Telecom Engineering Platform  (both left-aligned
   from the same left edge). */
.cmd-brand-text { display: flex; flex-direction: column-reverse; gap: 3.8px; /* was var(--sp-1)=4px, -5% */
  text-align: left; align-items: flex-start; }
.cmd-title {
  margin: 0; font-family: var(--font);
  font-size: 1.52rem; /* was 1.6rem, -5% */ font-weight: 600; letter-spacing: 0.02em; line-height: 1.05;
  text-transform: uppercase; color: var(--text);
  background: none; -webkit-background-clip: border-box; background-clip: border-box;
  -webkit-text-fill-color: var(--text);
}
/* Brand wordmark colors (color-only refinement; typography/spacing unchanged).
   KAREEM = soft pure white for stability; TEQ = subtle vertical cyan gradient
   for brand recognition without competing with the logo icon. */
.cmd-title-kareem { color: var(--text-primary); -webkit-text-fill-color: var(--text-primary); }
.cmd-title-teq {
  background: none;
  -webkit-background-clip: border-box; background-clip: border-box;
  color: var(--text-primary); -webkit-text-fill-color: var(--text-primary);
}
.cmd-sub {
  margin: 0; font-family: var(--font);
  font-size: 0.83125rem; /* was 0.875rem, -5% */ font-weight: 500; letter-spacing: 0.28em; line-height: 1.2;
  text-transform: none; color: var(--text-secondary);
}
/* 2026-08-04 explicit correction: keep the header identical on every page
   (same as the Dashboard/Home header) - the subtitle is NOT hidden on
   workspace pages, superseding the earlier per-page show/hide attempt. */

/* Right-side user controls: Home / user menu (avatar + name + chevron). */
.cmd-controls { flex: none; display: flex; align-items: center; gap: 13.3px; } /* orig 16px, -15%=14px, -5% */
/* Users (and Home) chip: clean transparent enterprise chip, no glow/shadow.
   Height/padding overridden below (not via --control-h, which is the
   app-wide control height used by buttons/inputs everywhere else) so this
   compaction stays scoped to the header only. */
.cmd-btn {
  display: inline-flex; align-items: center; gap: 5.7px; /* was 6px, -5% */
  background: transparent; border: 1px solid rgba(255,255,255,.06);
  border-radius: 11.4px; /* was 12px, -5% */ color: var(--text);
  font-family: var(--font); font-size: 0.890625rem; /* was 0.9375rem, -5% */ font-weight: 500;
  height: 32.3px; /* was 34px, -5% */ padding: 0 10.45px; /* was 0 11px, -5% */ cursor: pointer;
  user-select: none;
  transition: border-color 180ms ease, color 180ms ease, background 180ms ease;
}
/* Hover = slightly brighter surface + border, no accent glow. */
.cmd-btn:hover { background: rgba(255,255,255,.05); border-color: rgba(255,255,255,.12); color: var(--text); }
.cmd-btn .ic { width: 15.2px; height: 15.2px; } /* orig 18px -10%=16px, -5% */

/* ---- 6-column deck ---- */
.deck-grid {
  flex: 1 1 auto; min-height: 0;
  display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--sp-3);
}
.deck-col { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4) var(--sp-3); min-height: 0; position: relative; }
.deck-col-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding-bottom: var(--sp-3); border-bottom: 1px solid var(--border);
}
/* ---- Section status LEDs -------------------------------------------------
   Each section header shows a small colored dot that behaves like a calm
   hardware network-status LED: always lit, "breathing" via a very subtle
   brightness / glow / scale variation (max 105%) over ~4.5s. Muted enterprise
   colors, one per section, keyed off .deck-col[data-net] (set in main.js so the
   color follows the SECTION identity, not its position - correct even when a
   restricted user sees fewer columns). Honors prefers-reduced-motion. */
/* Official KAREEM TEQ dashboard indicator colors (one dedicated LED per
   technology section). Keyed off .deck-col[data-net] (set in main.js) so the
   color follows the SECTION identity, not its position. */
/* Brightness-matched palette: hues are the approved blue/emerald/cyan/amber/
   orange/slate, but each LED's lightness is tuned so all six share the same
   PERCEIVED brightness (~153 on the sqrt-luma scale) against the dark slate -
   otherwise amber/orange read much brighter than blue/slate. Hue family
   preserved; only lightness (and saturation minimally) adjusted. */
.deck-col[data-net="2G"]        { --led: #6C96F2; }  /* blue    */
.deck-col[data-net="3G"]        { --led: #10BE85; }  /* emerald */
.deck-col[data-net="4G"]        { --led: #06B1CE; }  /* cyan    */
.deck-col[data-net="5G"]        { --led: #CF8508; }  /* amber   */
.deck-col[data-net="Transport"] { --led: #F06606; }  /* orange  */
.deck-col[data-net="General"]   { --led: #8D9AAD; }  /* slate   */
.deck-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--led, var(--cyan));
  box-shadow: 0 0 5px color-mix(in srgb, var(--led, var(--cyan)) 45%, transparent);
  transform: translateZ(0);
  animation: led-breathe 4s ease-in-out infinite;
}
/* Independent hardware LEDs: negative delays so each section breathes out of
   phase with the others (and starts mid-cycle, already lit - no initial dim). */
.deck-col[data-net="2G"]        .deck-dot { animation-delay: -0.2s; }
.deck-col[data-net="3G"]        .deck-dot { animation-delay: -0.9s; }
.deck-col[data-net="4G"]        .deck-dot { animation-delay: -1.5s; }
.deck-col[data-net="5G"]        .deck-dot { animation-delay: -2.2s; }
.deck-col[data-net="Transport"] .deck-dot { animation-delay: -2.9s; }
.deck-col[data-net="General"]   .deck-dot { animation-delay: -3.5s; }
@keyframes led-breathe {
  0%, 100% { transform: scale(1);    opacity: .82;
    box-shadow: 0 0 4px color-mix(in srgb, var(--led, var(--cyan)) 38%, transparent); }
  50%      { transform: scale(1.05); opacity: 1;
    box-shadow: 0 0 8px color-mix(in srgb, var(--led, var(--cyan)) 62%, transparent); }
}
/* Disabled section (no permitted card in it): LED stays visible but dimmed,
   with no breathing and no glow - "the feature exists but is unavailable". */
.deck-col--locked .deck-dot {
  animation: none; transform: none; opacity: .4; box-shadow: none;
}
/* Accessibility: no motion -> static, fully-lit colored dot. */
@media (prefers-reduced-motion: reduce) {
  .deck-dot { animation: none; transform: none; opacity: 1;
    box-shadow: 0 0 4px color-mix(in srgb, var(--led, var(--cyan)) 40%, transparent); }
  .deck-col--locked .deck-dot { opacity: .4; box-shadow: none; }
}
.deck-col-title { margin: 0; font-size: 0.9375rem; font-weight: 800; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--text-2); }
.deck-stack { display: flex; flex-direction: column; gap: var(--sp-4); }

/* ---- Enterprise dashboard modules (premium telecom cards) ---------------- */
.deck-card {
  position: relative; overflow: hidden; cursor: pointer; width: 100%;
  display: flex; align-items: center; gap: 14px; text-align: left;
  min-height: 62px; padding: 13px 16px; color: var(--text); font-family: var(--font);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--panel);
  box-shadow: var(--sh-1);
  transition: transform 180ms ease, border-color 180ms ease,
              background 180ms ease, box-shadow 180ms ease;
  /* Hardware acceleration: promote to its own GPU layer so hover doesn't repaint/flicker. */
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
/* Aura glow: a soft blurred halo that blooms in behind the text on hover
   (the ".shine" span main.js already injects into every card - repurposed
   here instead of the old side-sweep animation). Fades in slowly (--t-slow)
   for the "calm, floating" feel; pure opacity transition, cheap to animate. */
/* No glow: the old aura sweep span stays in the DOM but is hidden. */
.deck-card .shine { display: none; }
.deck-card:hover {
  transform: translate3d(0, -2px, 0);
  border-color: var(--border-2);
  background: var(--surface-4);
  box-shadow: var(--sh-2);
}
.deck-card:active { transform: translate3d(0, 0, 0); }
/* Icon container = the visual identity of every module (uniform cyan). */
.deck-card-ic {
  position: relative; z-index: var(--z-raise);
  width: 36px; height: 36px; flex: none; display: inline-flex;
  align-items: center; justify-content: center; border-radius: var(--r-md);
  background: var(--accent-soft); border: 1px solid var(--accent-dk);
  color: var(--accent); box-shadow: none;
}
.deck-card:hover .deck-card-ic { color: var(--accent); box-shadow: none; }
.deck-card-ic .ic { width: 18px; height: 18px; }
.deck-card-title {
  position: relative; z-index: var(--z-raise);
  font-family: var(--font); font-weight: 600; font-size: 0.875rem;
  letter-spacing: 0.01em; color: var(--text);
}

/* ---- Locked cards (no permission) ---------------------------------------
   Card stays in place at full size/spacing but reads as unavailable: dimmed,
   muted icon+text, no hover/scale/glow, no pointer feedback. A subtle lock
   badge sits in the top-right corner. Interaction is blocked by the button's
   `disabled` attribute (set in main.js) - these rules just neutralize the
   hover styling that disabled buttons still match visually. */
.deck-card--locked {
  opacity: .65; cursor: not-allowed; box-shadow: var(--sh-1);
}
.deck-card--locked:hover {
  transform: translate3d(0, 0, 0);
  border-color: var(--border); background: var(--panel); box-shadow: var(--sh-1);
}
.deck-card--locked .deck-card-ic {
  background: var(--panel2); border-color: var(--border); color: var(--muted);
}
.deck-card--locked:hover .deck-card-ic { color: var(--muted); }
.deck-card--locked .deck-card-title { color: var(--text-2); }
.deck-card-lock {
  position: absolute; top: 10px; right: 10px; z-index: var(--z-raise-2);
  display: inline-flex; color: var(--muted); opacity: .7; pointer-events: none;
}
.deck-card-lock .ic { width: 14px; height: 14px; }

/* Phase 5 color unification kept a single teal accent for buttons/cards across
   the whole app. The section status LEDs (.deck-dot above) are the intentional
   exception: each carries its own muted enterprise color as an at-a-glance
   section identifier - the cards themselves stay on the unified teal accent. */

/* ============================================================================
   HOME - open "light-space" columns (HOME PAGE ONLY)
   Scoped to #app-shell.home-active so section pages keep the calm base theme.
   No boxed panel here anymore - just a hairline divider between columns that
   fades at the top/bottom (::after on the gap, no extra markup needed).
   ============================================================================ */
#app-shell.home-active {
  background: var(--bg-body);
}
/* 3D perspective so cards still read as raised tiles floating in the space. */
#app-shell.home-active .deck-grid { perspective: 1500px; }

#app-shell.home-active .deck-col {
  background: transparent; border: none; box-shadow: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
#app-shell.home-active .deck-col:not(:last-child)::after {
  content: ""; position: absolute; top: 8%; bottom: 8%; right: -7px; width: 1px;
  background: linear-gradient(to bottom, transparent, var(--border-2) 25%, var(--border-2) 75%, transparent);
}
#app-shell.home-active .deck-card { transform-style: preserve-3d; }

.hidden { display: none !important; }

/* ============================ Auth / login ============================ */
/* ---- Enterprise split-screen login ---- */
/* Login page background: full-bleed engineer/equipment photo (page-level,
   behind both panels - one continuous scene, not two separate fills), one
   graded photo per theme. Left-bottom anchored so "cover" only ever crops
   the empty sky/right area, never the engineer figure. A soft directional
   overlay on top guarantees text/card contrast regardless of viewport ratio
   (spec: adjust background, never the UI). */
.login-overlay {
  position: fixed; inset: 0; z-index: var(--z-login);
  background: var(--bg-body);
}
:root:not([data-theme="light"]) .login-overlay {
  background:
    linear-gradient(90deg, rgba(11,19,29,.15) 0%, rgba(11,19,29,.55) 62%, rgba(11,19,29,.8) 100%),
    url("/static/login-bg-dark.jpg?v=21") left bottom / cover no-repeat,
    var(--bg-body);
}
:root[data-theme="light"] .login-overlay {
  background:
    linear-gradient(90deg, rgba(210,214,216,.1) 0%, rgba(210,214,216,.5) 62%, rgba(210,214,216,.78) 100%),
    url("/static/login-bg-light.jpg?v=14") left bottom / cover no-repeat,
    var(--bg-body);
}
.login-split { display: flex; width: 100%; height: 100%; }

/* LEFT: brand panel (60%) - transparent, sits on the shared page photo.
   Top-anchored (not vertically centered) with generous top padding so the
   logo/name/headline stay clear of the engineer figure lower in the frame. */
.login-left {
  flex: 0 0 60%; position: relative; overflow: hidden;
  display: flex; align-items: flex-start; padding: 2.5vh 6vw 0;
  border-right: 1px solid rgba(255,255,255,.12);
}
:root[data-theme="light"] .login-left { border-right-color: var(--border); }
/* Compact, top-anchored block - the photo's equipment cabinet starts around
   40% down the panel, so this must finish well above that with clear air
   below it (spec: gap between the text and the figure, kept "احترافي"). */
.login-left-content { position: relative; z-index: var(--z-raise); max-width: 950px; }
.login-hero-logo { height: 105.6px; width: auto; display: block; margin-bottom: 13px; }
.login-hero-name { margin: 0; font-family: var(--font); font-weight: 700; font-size: 1.5928rem;
  letter-spacing: 0.02em; line-height: 1.05; text-transform: uppercase; color: var(--text); }
.login-hero-tag { margin: 4px 0 0; font-family: var(--font); font-weight: 500; font-size: 0.668rem;
  letter-spacing: 0.24em; color: var(--text-2); }
.login-hero-headline { margin: 9px 0 5px; font-family: var(--font); font-weight: 700;
  font-size: 1.1025rem; line-height: 1.25; color: var(--text); max-width: 950px; white-space: nowrap; }
.login-hero-desc { margin: 0; font-family: var(--font); font-weight: 400; font-size: 0.779rem;
  line-height: 1.5; color: var(--text-2); max-width: 920px; white-space: nowrap; }

/* RIGHT: centered login card (40%) - transparent, sits on the same shared
   page photo as .login-left (its right side is already near-solid dark in
   the source photo). Small extra darkening behind the card guarantees
   contrast even where "cover" crops less-dark parts of the photo in here -
   also the ONLY background mobile sees once .login-left is hidden below. */
.login-right {
  flex: 1 1 40%; display: flex; align-items: center; justify-content: center; padding: var(--sp-10);
  background: radial-gradient(560px 420px at 50% 42%, rgba(5,10,16,.35), transparent 65%);
}
:root[data-theme="light"] .login-right {
  background: radial-gradient(560px 420px at 50% 42%, rgba(20,30,35,.08), transparent 65%);
}

.login-card {
  width: 420px; max-width: 460px; box-sizing: border-box; position: relative;
  background: var(--panel); border: 1px solid rgba(255,255,255,.06);
  border-radius: 20px; padding: 40px 36px; box-shadow: 0 24px 60px -22px rgba(0,0,0,.6);
  display: flex; flex-direction: column; animation: pop-in var(--t) both;
}

/* Sun/moon theme toggle, sitting right on the login card's own top-right
   corner (not floating separately in the panel) - icon shown is the theme a
   click switches TO (sun while dark, moon while light), matching the
   Settings page's existing setTheme()/getTheme() (main.js) so both controls
   always agree. */
.login-theme-toggle {
  position: absolute; top: 6px; right: 6px; z-index: var(--z-raise);
  width: 36px; height: 36px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel2); border: 1px solid var(--border);
  color: var(--text-2); cursor: pointer; transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.login-theme-toggle:hover { background: var(--surface-4); color: var(--text); }
.login-theme-toggle svg { width: 18px; height: 18px; }

/* Header sun/moon theme toggle (cmd-controls, far right) - same icon-swap
   pattern and setTheme()/getTheme() as the login page's toggle, just an
   inline flex chip instead of an absolutely-positioned corner button since
   it lives in the header's control row alongside Home/Account. */
.cmd-theme-toggle {
  flex: none; width: 32.3px; height: 32.3px; /* was 34px, -5% */ border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid rgba(255,255,255,.06);
  color: var(--text-2); cursor: pointer;
  transition: border-color 180ms ease, color 180ms ease, background 180ms ease;
}
.cmd-theme-toggle:hover { background: var(--surface-4); color: var(--text); }
.cmd-theme-toggle svg { width: 17.1px; height: 17.1px; } /* was 18px, -5% */

.login-theme-toggle .ico-moon, .cmd-theme-toggle .ico-moon { display: none; }
:root[data-theme="light"] .login-theme-toggle .ico-sun,
:root[data-theme="light"] .cmd-theme-toggle .ico-sun { display: none; }
:root[data-theme="light"] .login-theme-toggle .ico-moon,
:root[data-theme="light"] .cmd-theme-toggle .ico-moon { display: block; }
.login-card-logo { height: 107px; width: auto; align-self: flex-start; margin-bottom: 22px; }
.login-title { margin: 0; font-family: var(--font); font-weight: 700; font-size: 1.875rem;
  line-height: 1.1; color: var(--text); }
.login-desc { margin: 8px 0 26px; font-family: var(--font); font-size: 0.9375rem; color: var(--text-2); }
.login-card label { font-family: var(--font); font-weight: 500; font-size: 0.875rem; color: var(--text-2);
  margin: 16px 0 8px; }
.login-card input {
  height: 46px; box-sizing: border-box; width: 100%;
  background: var(--panel2); border: 1px solid var(--border); border-radius: 12px;
  color: var(--text); padding: 0 14px; font-size: 0.9375rem; outline: none;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
.login-card input:focus { border-color: var(--accent); box-shadow: var(--ring); background: var(--panel); }
/* Extra .btn.accent qualifier so these beat the global accent glow (specificity). */
.btn.accent.login-btn {
  margin-top: 28px; width: 100%; height: 48px; justify-content: center;
  border-radius: 12px; font-size: 0.9375rem; font-weight: 600; padding: 0; box-shadow: none;
}
.btn.accent.login-btn:hover { background: var(--accent-hv); border-color: var(--accent-hv); box-shadow: none; }
.login-err { color: var(--error); font-size: 0.8125rem; min-height: 18px; margin-top: 14px; text-align: center; }
.login-foot {
  display: flex; justify-content: space-between; gap: var(--sp-3);
  margin-top: 26px; padding-top: 18px; border-top: 1px solid var(--border);
  font-family: var(--font); font-size: 0.75rem; color: var(--muted);
}

/* ============================ User bar (top-right header) ============================ */
.topuser {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end;
  gap: var(--sp-3); margin-bottom: 0;
}
/* App brand in the top-left corner -> click returns to Home launcher. */
.app-brand {
  margin-right: auto;               /* push brand left, keep user controls right */
  display: inline-flex; align-items: center; gap: 10px;
  cursor: pointer; padding: 5px 10px; border-radius: var(--r-md);
  transition: background var(--t-fast);
}
.app-brand:hover { background: var(--panel2); }
/* NOTE: .app-brand-logo / .logo-badge / .logo-mono below are not used by the
   current header (templates/index.html uses .cmd-header/.cmd-brand instead)
   - kept only so this legacy block still has valid, themed colors if reused. */
.app-brand-logo { display: inline-flex; }
.app-brand-logo svg { display: block; }
.app-brand-logo .logo-badge { fill: var(--panel); stroke: var(--border-2); stroke-width: 1.2; }
.app-brand-logo .logo-mono { fill: none; stroke-width: 4.1;
  stroke-linecap: round; stroke-linejoin: round; }
.app-brand-text { display: flex; flex-direction: column; line-height: 1.12; }
.app-brand-name { font-size: 1.6875rem; font-weight: 800; letter-spacing: .6px; color: var(--text); }
.app-brand-sub { font-size: 0.875rem; font-weight: 600; color: var(--muted); letter-spacing: 1.2px;
  text-transform: uppercase; }
.userbar-name { font-weight: 600; font-size: 0.855rem; /* was 0.9rem, -5% */ color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 180px; }
/* .userbar-role now lives inside .user-menu-header (the dropdown's name/role
   block, 2026-08-04 "Header & User Profile Refinement") - the trigger only
   shows the avatar + name + chevron now, no role. Back to a roomier size
   since it's no longer squeezed into the compact trigger. */
.userbar-role { font-size: 0.75rem; font-weight: 500; color: var(--text-2); }
/* Logout: 40x40 icon-only chip, matches the Users chip; neutral brighter hover. */
.userbar-btn {
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid rgba(255,255,255,.06); border-radius: 12px;
  color: var(--text-2); width: var(--control-h); height: var(--control-h); cursor: pointer;
  transition: border-color 180ms ease, color 180ms ease, background 180ms ease;
}
.userbar-btn:hover { background: rgba(255,255,255,.05); border-color: rgba(255,255,255,.12); color: var(--text); }
.userbar-btn .ic { width: 18px; height: 18px; }
/* ---- Account dropdown (Admin -> Users/Logout ; User -> Logout) --------------
   Replaces the old standalone Users button + inline name/role + icon-only
   logout with a single enterprise chip trigger that opens an anchored menu.
   Reuses the design tokens/hover of .cmd-btn / .userbar-btn so nothing
   about the header height, colors or typography changes. */
.user-menu { position: relative; display: inline-flex; align-items: center; }
/* Height/padding overridden here rather than via --control-h, same reason
   as .cmd-btn above - this stays a header-only compaction. */
/* Trigger: avatar + name + chevron only now ("Kareem" not "Kareem /
   Administrator") - role moved into the dropdown's own header block below. */
.user-menu-trigger {
  display: inline-flex; align-items: center; gap: 5.7px; /* was 6px, -5% */
  background: transparent; border: 1px solid rgba(255,255,255,.06);
  border-radius: 11.4px; /* was 12px, -5% */ color: var(--text);
  font-family: var(--font); height: 32.3px; /* was 34px, -5% */ padding: 0 7.6px; /* was 0 8px, -5% */ cursor: pointer;
  user-select: none;
  transition: border-color 180ms ease, color 180ms ease, background 180ms ease;
}
.user-menu-trigger:hover,
.user-menu.open .user-menu-trigger { background: rgba(255,255,255,.05); border-color: rgba(255,255,255,.12); }
.user-menu-avatar { width: 15.2px; height: 15.2px; color: var(--text-2); } /* was 16px, -5% */
.user-menu-chev { width: 12.35px; height: 12.35px; color: var(--text-2); transition: transform 180ms ease; } /* was 13px, -5% */
.user-menu.open .user-menu-chev { transform: rotate(180deg); }
.user-menu-panel {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: var(--z-popover);
  min-width: 200px; padding: 6px;
  background: var(--panel); border: 1px solid var(--border-2);
  border-radius: var(--r-md); box-shadow: var(--sh-2);
  display: flex; flex-direction: column; gap: 2px;
}
/* Name/role header block at the top of the dropdown (GitHub/GitLab/Linear-
   style account menu, 2026-08-04) - same left/right padding as the action
   rows below it for equal-row rhythm across the whole panel. */
.user-menu-header {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 8px 12px;
}
.user-menu-header-name { font-family: var(--font); font-size: 0.95rem; font-weight: 600; color: var(--text); }
.user-menu-item {
  display: inline-flex; align-items: center; gap: 10px;
  background: transparent; border: 0; border-radius: 8px;
  color: var(--text); font-family: var(--font); font-size: 0.875rem; font-weight: 500;
  padding: 9px 12px; cursor: pointer; text-align: left; width: 100%;
  user-select: none;
  transition: background 140ms ease, color 140ms ease;
}
.user-menu-item .ic { width: 16px; height: 16px; color: var(--text-2); }
.user-menu-item:hover { background: var(--accent); color: var(--on-accent); }
.user-menu-item:hover .ic { color: var(--on-accent); }
.user-menu-sep { height: 1px; margin: 4px 6px; background: var(--border); }
.topuser-link {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-md);
  color: var(--text-2); font-family: var(--font); font-size: 0.8125rem; font-weight: 600;
  height: 34px; padding: 0 13px; cursor: pointer;
  transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.topuser-link:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* ============================ Users admin panel ============================ */
.users-panel { display: flex; flex-direction: column; gap: var(--sp-5); max-width: 1520px; }
/* Users page Search & Filters (Phase 1 restructure, 2026-08-17): Search
   grows to fill the row, Role stays a compact dropdown alongside it. */
.users-filter-grid { gap: 16px 28px; align-items: flex-end; }
.users-filter-grid .field:first-child { flex: 1 1 320px; }
.users-filter-grid .field:last-child { flex: 0 1 200px; }
/* Table scroll container: keeps horizontal overflow inside the table on narrow
   screens so the page itself never scrolls sideways. min-width keeps columns
   readable rather than crushing engineering values. */
.table-scroll { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--r-lg); }
.table-scroll .users-table { min-width: 520px; }
.users-table { width: 100%; border-collapse: collapse; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
.users-table th, .users-table td {
  text-align: left; padding: 12px 14px; border-bottom: 1px solid var(--border); font-size: 0.8125rem;
  color: var(--text-value); font-weight: 500;   /* Table values (L5) */
}
.users-table tr:last-child td { border-bottom: none; }
.users-table tr { transition: background var(--t-fast); }
.users-table tbody tr:nth-child(even) td { background: rgba(255,255,255,.02); }   /* subtle row alternation */
.users-table tbody tr:hover td { background: var(--surface-4); }                  /* slate highlight */
.users-table th { color: var(--text-secondary); font-weight: 600; font-size: 0.6875rem; text-transform: uppercase; letter-spacing: .6px; background: var(--panel2); }   /* Table header */
.role-badge { display: inline-block; padding: 3px 10px; border-radius: var(--r-pill); font-size: 0.6875rem; font-weight: 700; }
.role-admin { background: var(--accent-dk); color: var(--accent); }
.role-user  { background: rgba(255,255,255,.05); color: var(--text-2); }
.btn-danger {
  background: transparent; border: 1px solid var(--error); color: var(--error);
  border-radius: var(--r-sm); padding: 6px 12px; cursor: pointer; font-size: 0.75rem; font-weight: 600;
  user-select: none;
  transition: background var(--t-fast), color var(--t-fast);
}
.btn-danger:hover { background: var(--error); color: var(--text); }
.btn-danger:disabled { opacity: .35; cursor: not-allowed; }
.btn-danger:disabled:hover { background: transparent; color: var(--error); }
/* keep Edit + Delete a neat, close pair (they were touching) */
.users-table td:last-child { white-space: nowrap; }
.users-table td button + button { margin-left: 6px; }
.muted-note { color: var(--muted); font-size: 0.75rem; }

/* ============================ Modal ============================ */
.modal-overlay {
  position: fixed; inset: 0; z-index: var(--z-modal); display: flex; align-items: center; justify-content: center;
  background: rgba(9,15,23,.66); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  animation: fade var(--t-fast) both;
}
.modal-card {
  width: 340px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-xl);
  padding: var(--sp-6); box-shadow: var(--sh-3); display: flex; flex-direction: column;
  backdrop-filter: blur(30px); -webkit-backdrop-filter: blur(30px);
  animation: pop-in var(--t) both;
  /* Same fix as .user-drawer above (reported live, same symptom on the new
     Add Access / confirm dialogs): titles/labels/buttons aren't selectable
     text, so hovering them shouldn't flash a text (I-beam) cursor. Text
     inputs opt back in below. */
  user-select: none;
}
.modal-card input[type=text],
.modal-card input[type=password],
.modal-card input[type=number] { user-select: text; }
.modal-card h3 { margin: 0 0 var(--sp-4); font-size: 1rem; font-weight: 700; color: var(--text); }
.modal-card label { font-weight: 500; font-size: 0.75rem; color: var(--text-label); margin: var(--sp-2) 0 6px; }   /* L4 label */
.modal-card input, .modal-card select {
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-md);
  color: var(--text-value); font-weight: 500; padding: 10px 12px; font-size: 0.875rem; outline: none;   /* L5 value */
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.modal-card input:focus, .modal-card select:focus { border-color: var(--accent); box-shadow: var(--ring); }
.modal-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-5); }
.modal-actions .btn { flex: 1; justify-content: center; }
.modal-err { color: var(--error); font-size: 0.75rem; min-height: 16px; margin-top: var(--sp-2); }

/* ==================== Section-access picker ==================== */
.access-block { margin-top: var(--sp-4); border-top: 1px solid var(--border); padding-top: var(--sp-3); }
.access-head {
  display: flex; align-items: center; gap: 6px; margin-bottom: 6px;
  font-weight: 700; font-size: 0.75rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .5px;
}
.access-head span { margin-right: auto; }
/* in the Edit modal, push the ALLOWED SECTIONS row (All/None) away from the
   password box above it */
.modal-card .access-head { margin-top: var(--sp-5); }
.btn-mini {
  background: transparent; border: 1px solid var(--border); color: var(--text-2);
  border-radius: var(--r-sm); padding: 4px 10px; cursor: pointer; font-size: 0.6875rem; font-weight: 600;
  user-select: none;
  transition: border-color var(--t-fast), color var(--t-fast);
}
.btn-mini:hover { border-color: var(--accent); color: var(--accent); }
.btn-mini:disabled { opacity: .35; cursor: not-allowed; }
.sec-checks { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 16px; }
.sec-check { display: flex; align-items: center; gap: var(--sp-2); padding: 5px 2px; cursor: pointer; border-radius: var(--r-sm); }
.sec-check input[type=checkbox] {
  padding: 0; cursor: pointer; flex: none;
}
.sec-check span { font-size: 0.8125rem; color: var(--text); font-weight: 500; }
.access-cell { color: var(--text-label); font-size: 0.75rem; }   /* secondary info */
.modal-note { color: var(--muted); font-size: 0.75rem; margin: var(--sp-2) 0 2px; }

/* ==================== One Click IP - Node selection modal ====================
   Single-choice radio list rendered inside .modal-card. Uses tokenised colors
   only (no new palette). Native <input type=radio> is visually hidden and
   replaced by .ocip-node-dot; keyboard focus / a11y still work via the input. */
.ocip-node-modal { width: 380px; }
.ocip-node-radio-group {
  display: flex; flex-direction: column; gap: var(--sp-2);
  margin: var(--sp-3) 0 var(--sp-2);
}
.ocip-node-radio {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--panel2); cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.ocip-node-radio:hover { border-color: var(--border-2); }
.ocip-node-radio input[type="radio"] {
  position: absolute; opacity: 0; pointer-events: none;
  width: 0; height: 0; margin: 0; padding: 0;
}
.ocip-node-dot {
  flex: 0 0 18px; width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid var(--border-2); background: transparent;
  position: relative; transition: border-color var(--t-fast);
}
.ocip-node-dot::after {
  content: ""; position: absolute; inset: 3px; border-radius: 50%;
  background: var(--accent); transform: scale(0);
  transition: transform var(--t-fast);
}
.ocip-node-radio.is-selected { border-color: var(--accent); background: var(--surface-4); }
.ocip-node-radio.is-selected .ocip-node-dot { border-color: var(--accent); }
.ocip-node-radio.is-selected .ocip-node-dot::after { transform: scale(1); }
.ocip-node-radio input[type="radio"]:focus-visible + .ocip-node-dot { box-shadow: var(--ring); }
.ocip-node-body { display: flex; flex-direction: column; gap: 2px; }
.ocip-node-lbl { color: var(--text); font-weight: 600; font-size: 0.875rem; }
.ocip-node-count { color: var(--muted); font-size: 0.75rem; }

/* Rule 3: per-technology sections inside the node selection modal. Each tech
   gets its own labelled block with a 2-option radio row (1st / 2nd Node). */
.ocip-tech-list { display: flex; flex-direction: column; gap: var(--sp-3); margin: var(--sp-3) 0 var(--sp-2); }
.ocip-tech-box {
  display: flex; flex-direction: column; gap: var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3); background: var(--panel);
}
.ocip-tech-name {
  color: var(--accent); font-weight: 700; font-size: 0.75rem;
  text-transform: uppercase; letter-spacing: .8px;
}
.ocip-tech-box .ocip-node-radio-group { flex-direction: row; margin: 0; gap: var(--sp-2); }
.ocip-tech-box .ocip-node-radio { flex: 1; padding: 8px 12px; }

/* Rule 7: Continue / Cancel dialog for missing required IP Types. Same shell
   as .ocip-node-modal so the interaction feels identical. The list is a
   compact monospace roster of the missing canonical names. */
.ocip-missing-modal { width: 420px; }
.ocip-missing-list {
  margin: var(--sp-2) 0 var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  list-style: none;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--panel);
  max-height: 220px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 6px;
}
.ocip-missing-list li {
  color: var(--text); font-family: var(--mono);
  font-size: 0.8125rem; font-weight: 600;
}
.ocip-missing-list li::before {
  content: "\2022";
  display: inline-block; width: 14px;
  color: var(--accent);
}

/* ============== Grouped permission picker (Users page + Edit modal) =========
   Technology-grouped containers matching the dashboard order & LEDs. Each group
   is self-contained so new tools drop in without any redesign. */
/* Two fully independent vertical stacks, not a shared-row CSS grid -
   explicit user request: expanding a card in the left column must not
   move ANYTHING in the right column (or vice versa), not even push it
   down. sectionChecks() (main.js) alternates groups left/right by index,
   reproducing the same default (all-collapsed) pairing as before. */
.perm-grid { display: flex; gap: var(--sp-3); align-items: flex-start; }
.perm-col { display: flex; flex-direction: column; gap: var(--sp-3); flex: 1 1 0; min-width: 0; }
.perm-group {
  display: flex; flex-direction: column; gap: 10px;
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--panel); box-shadow: var(--sh-1);
  padding: 14px 16px 12px;
}
/* Section status LED colors (identical palette to the dashboard columns). */
.perm-group[data-net="2G"]        { --led: #6C96F2; }
.perm-group[data-net="3G"]        { --led: #10BE85; }
.perm-group[data-net="4G"]        { --led: #06B1CE; }
.perm-group[data-net="5G"]        { --led: #CF8508; }
.perm-group[data-net="Transport"] { --led: #F06606; }
.perm-group[data-net="General"]   { --led: #8D9AAD; }
.perm-group[data-net="2G"]        .deck-dot { animation-delay: -0.2s; }
.perm-group[data-net="3G"]        .deck-dot { animation-delay: -0.9s; }
.perm-group[data-net="4G"]        .deck-dot { animation-delay: -1.5s; }
.perm-group[data-net="5G"]        .deck-dot { animation-delay: -2.2s; }
.perm-group[data-net="Transport"] .deck-dot { animation-delay: -2.9s; }
.perm-group[data-net="General"]   .deck-dot { animation-delay: -3.5s; }
.perm-group-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding-bottom: 10px; border-bottom: 1px solid var(--border);
}
/* Admin Access (User Drawer, super-admin only) - literally a .perm-group
   card (same markup/classes, see openUserDrawer), just with its own LED
   color since it isn't one of the [data-net] networks below. Red/--error
   specifically because it's not used by any existing network dot, so it
   reads as visually distinct at a glance (explicit user request). */
.admin-access-card { --led: var(--error); }
.perm-group-title {
  font-size: 0.75rem; font-weight: 800; letter-spacing: 1px;
  text-transform: uppercase; color: var(--text-2); white-space: nowrap;
}
.perm-count { margin-left: auto; font-size: 0.6875rem; font-weight: 600; color: var(--muted); white-space: nowrap; }
.perm-all {
  flex: none; background: transparent; border: 1px solid var(--border); color: var(--text-2);
  border-radius: var(--r-sm); padding: 4px 13px 4px 10px; cursor: pointer;
  font-family: var(--font); font-size: 0.6875rem; font-weight: 600;
  transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.perm-all:hover { border-color: var(--accent); color: var(--accent); }
.perm-all:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
.perm-all.is-clear { color: var(--muted); }
.perm-list { display: flex; flex-direction: column; gap: 2px; }
/* Custom KAREEM TEQ checkbox row (no native checkbox). */
.perm-item {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 8px; border-radius: var(--r-md); cursor: pointer;
  transition: background var(--t-fast);
  /* Without this, the label's text content is selectable, and Windows/
     Chrome can flash a text (I-beam) cursor over it before the pointer
     cursor above takes over - reported live, toggle row shows a text
     cursor on hover. This is a toggle control, not selectable text. */
  user-select: none;
}
.perm-item:hover { background: var(--surface-4); }
/* The native checkbox is fully hidden - the visible control is the adjacent
   .perm-check pill. !important is required because the global
   `input[type=checkbox]:disabled { opacity: .5 }` rule (higher specificity)
   would otherwise un-hide it when the picker is shown read-only (a regular
   admin viewing an admin account, incl. their own) - the hidden input has
   no positioned ancestor, so at opacity .5 it surfaced as a stray grey dot
   near the drawer's top-left corner, overlapping the card headers. Found
   live via a regular admin editing their own account. */
.perm-item input {
  position: absolute; opacity: 0 !important; width: 0; height: 0; margin: 0; pointer-events: none;
}
/* Custom permission checkbox rendered as the same global ON/OFF toggle. The
   native <input> stays hidden (.perm-item input) and drives the pill via the
   adjacent .perm-check; only the visual changed - selection logic is untouched. */
.perm-check {
  flex: none; position: relative; width: 34.2px; height: 19.8px; border-radius: var(--r-pill); /* -10%, app-wide */
  border: none; background: var(--switch-off);
  display: inline-flex; align-items: center;
  transition: background var(--t-fast);
}
.perm-check::after {
  content: ""; position: absolute; top: 1.8px; left: 1.8px;
  width: 16.2px; height: 16.2px; border-radius: 50%;
  background: var(--switch-knob); box-shadow: 0 1px 2px rgba(0,0,0,.35);
  transition: transform var(--t-fast);
}
.perm-check .ic { display: none; }   /* toggle uses a sliding thumb, not a checkmark */
.perm-item input:checked + .perm-check { background: var(--accent); }
.perm-item input:checked + .perm-check::after { transform: translateX(14.4px); }
.perm-item input:focus-visible + .perm-check { box-shadow: var(--ring); }
.perm-label { font-size: 0.8125rem; font-weight: 500; color: var(--text); }
/* Edit modal hosts the same picker: widen it (already two independent
   columns via .perm-col, see above - no per-context column count to set). */
.modal-card:has(.perm-grid) { width: 640px; max-height: 90vh; overflow-y: auto; }

/* Collapsed-by-default groups (Phase 2 User Drawer, 2026-08-17): the header
   row is clickable, .perm-list only shows once its .perm-group has
   .expanded. Chevron rotates 90deg to point down when open. */
.perm-group-head { cursor: pointer; user-select: none; }
.perm-group-chev { flex: none; width: 14px; height: 14px; color: var(--muted); transition: transform var(--t-fast); }
.perm-group.expanded .perm-group-chev { transform: rotate(90deg); }
.perm-group .perm-list { display: none; }
.perm-group.expanded .perm-list { display: flex; }
.perm-group.expanded .perm-group-head { border-bottom-color: var(--border); }

/* ==================== User Drawer (Phase 2, 2026-08-17) ====================
   Add/Edit User - a right-side panel instead of a centered modal. Reuses
   .modal-overlay's fixed backdrop (same dimming/blur/z-index/Escape
   convention as every other modal here) with a second class that pushes the
   panel to the right edge and stretches it full height, instead of
   centering a fixed-size card. */
.user-drawer-overlay { justify-content: flex-end; align-items: stretch; }
.user-drawer {
  width: 774px; /* 704px + 10%, explicit follow-up request - extra breathing
                   room for the permission cards' Select All button/chevron
                   spacing. Username/Password/Role still fit on one row, see
                   .user-drawer-account-grid/.user-drawer-field-col below */
  max-width: 100vw; height: 100%;
  background: var(--panel); border-left: 1px solid var(--border);
  box-shadow: var(--sh-3); display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform var(--t) ease;
  /* This is a control panel, not a document - titles/labels/counts/badges
     shouldn't act like selectable text (reported live: text/I-beam cursor
     flashing all over the panel on hover). Text inputs opt back in below,
     they're the only place selecting text actually makes sense here. */
  user-select: none;
}
.user-drawer input[type=text],
.user-drawer input[type=password] { user-select: text; }
.user-drawer-overlay.open .user-drawer { transform: translateX(0); }
.user-drawer-header {
  flex: none; position: relative; padding: var(--sp-6) var(--sp-6) var(--sp-4);
  border-bottom: 1px solid var(--border);
}
.user-drawer-header h3 { margin: 0; font-size: 1.0625rem; font-weight: 700; color: var(--text); padding-right: 32px; }
.user-drawer-sub { margin: 4px 0 0; font-size: 0.8125rem; color: var(--muted); }
.user-drawer-close {
  position: absolute; top: var(--sp-5); right: var(--sp-5);
  width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
  background: transparent; border: none; color: var(--muted); cursor: pointer; border-radius: var(--r-sm);
  transition: background var(--t-fast), color var(--t-fast);
}
.user-drawer-close:hover { background: var(--surface-4); color: var(--text); }
.user-drawer-close .ic { width: 16px; height: 16px; }
.user-drawer-body { flex: 1 1 auto; overflow-y: auto; padding: var(--sp-5) var(--sp-6); }
.user-drawer-section + .user-drawer-section { margin-top: var(--sp-6); }
.user-drawer-section-title {
  font-size: 0.75rem; font-weight: 800; letter-spacing: .6px; text-transform: uppercase;
  color: var(--muted); margin-bottom: var(--sp-3);
}
.user-drawer-body .field { margin-bottom: var(--sp-4); }
/* Folder Properties (Roles/File-Permissions spec, 18 Aug 2026): the "+ Add
   User" button had no gap before the access table below it - reported live,
   the row felt jammed right up against the button. */
.fprops-add-btn { margin-bottom: var(--sp-4); }
.fprops-table-wrap:empty { display: none; }
/* Explicit user request: the highlight should hug the toggle+label only,
   not stretch across the row. .perm-item is a flex element placed directly
   in normal block flow here (its parent is a plain .user-drawer-section
   div, not a stretching .perm-list) - a block-level flex container defaults
   to filling its containing block's width just like any other block box,
   so it needs an explicit shrink-to-fit width to hug its content instead. */
.fprops-hide-row { margin-top: var(--sp-3); width: fit-content; }
@media (max-width: 1023px) and (min-width: 768px) {
  .user-drawer-body .perm-grid { flex-direction: column; }
}
/* Account fields row: same compact field size as everywhere else in the
   app (not stretched to fill the wider drawer) - explicit follow-up
   request, "keep the boxes their old size, just rearrange them side by
   side in the new width, one row not stacked". Each column keeps its
   field's own error message directly under it (spec: errors appear under
   their field), so the flex item is the column wrapper, not the field
   itself. */
/* Equal-width columns that share the drawer's full row (no max-width cap) -
   explicit follow-up request to stop the fields clumping on the left with
   empty space to the right; they now spread evenly with a wider, more even
   gap between them. Still wraps to a single column on narrow/mobile widths
   via the 165px min basis. */
.user-drawer-account-grid { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--sp-3) var(--sp-5); }
.user-drawer-field-col { flex: 1 1 165px; }
.user-drawer-field-col .field { margin-bottom: 0; }
/* .field-err's -10px top margin (below) assumes a field with its normal
   margin-bottom (var(--sp-4), 16px) above it - it eats into that gap,
   leaving ~6px clearance elsewhere in the app. Inside the drawer's account
   row that margin-bottom is zeroed (rule above, columns use their own gap
   instead), so the same -10px had nothing to eat into and instead bit
   straight into the input box itself - the red error text visibly
   overlapped the bottom ~10px of the Username/Password input, reported
   live. Overriding it back to a small positive gap here fixes that without
   touching the shared .field-err rule everywhere else it's used. */
.user-drawer-field-col .field-err { margin-top: 4px; }
/* padding-right leaves a safety margin before the next column - without it
   the "Password must be at least 4 characters." message sits within under
   1px of its column's edge (font-rendering-dependent), which can spill into
   the Role column on some browsers - reported live on the deployed drawer. */
.field-err { color: var(--error); font-size: 0.75rem; margin: -10px 0 var(--sp-3); min-height: 0; padding-right: 10px; }
.field-err:empty { display: none; }
.user-drawer-pw-wrap { position: relative; display: flex; }
.user-drawer-pw-wrap input { flex: 1 1 auto; padding-right: 40px; }
.user-drawer-pw-toggle {
  position: absolute; top: 0; right: 0; width: 40px; height: 100%;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: none; color: var(--muted); cursor: pointer;
}
.user-drawer-pw-toggle:hover { color: var(--text); }
.user-drawer-pw-toggle .ic { width: 16px; height: 16px; }
.user-drawer-footer {
  flex: none; display: flex; gap: var(--sp-3); padding: var(--sp-4) var(--sp-6);
  border-top: 1px solid var(--border); background: var(--panel);
}
.user-drawer-footer .btn { flex: 1; justify-content: center; }
@media (max-width: 1023px) and (min-width: 768px) {
  .user-drawer { width: 620px; }
}
@media (max-width: 767px) {
  .user-drawer { width: 100vw; }
}

/* ==================== Activity Log (Phase 3, admin-only) ====================
   Reuses .users-panel/.card/.grid/.field/.users-table/.table-scroll/.btn-mini
   as-is - only the pieces below are net-new (summary cards, status pills,
   pagination bar, and the narrower read-only details drawer). */
.activity-summary-cards {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-4);
}
.activity-summary-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-5); display: flex; flex-direction: column; gap: 4px;
}
.activity-summary-value { font-size: 1.75rem; font-weight: 800; color: var(--text); }
.activity-summary-card.ok .activity-summary-value { color: var(--ok); }
.activity-summary-card.fail .activity-summary-value { color: var(--error); }
.activity-summary-card.sec .activity-summary-value { color: var(--accent); }
.activity-summary-label { font-size: 0.75rem; color: var(--muted); font-weight: 600; }

/* Fixed 5-column grid (9 fields -> a clean 2 rows: 5 then 4) instead of the
   generic flex-wrap .grid, whose wrap point drifts with viewport width. Kept
   at 5 columns down to the tablet breakpoint (explicit user request: "two
   lines"), only dropping to fewer columns once 5 genuinely can't fit. */
.activity-filter-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 16px 28px; }
.activity-filter-grid .field { min-width: 0; max-width: none; width: 100%; }
.activity-filter-actions { display: flex; justify-content: flex-end; gap: var(--sp-3); margin-top: var(--sp-4); }
@media (max-width: 767px) {
  .activity-filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.activity-row { cursor: pointer; }
.activity-status {
  display: inline-block; padding: 3px 10px; border-radius: var(--r-pill); font-size: 0.6875rem; font-weight: 700;
}
.activity-status.success { background: rgba(47,174,106,.14); color: var(--ok); }
.activity-status.failed  { background: var(--error-soft); color: var(--error); }

.activity-empty { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); padding: var(--sp-6) 0; text-align: center; }

.activity-pagination {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--sp-3);
  padding-top: var(--sp-4); margin-top: var(--sp-2); border-top: 1px solid var(--border);
}
.activity-pagination-count { font-size: 0.75rem; color: var(--muted); }
.activity-pagination-nav { display: flex; align-items: center; gap: var(--sp-3); }
.activity-pagination-page { font-size: 0.75rem; color: var(--text-2); }

/* Read-only Activity Details panel - same slide-in mechanism as .user-drawer,
   just narrower (it has no form, just label/value pairs). */
.activity-details-drawer { width: 480px; }
.activity-detail-row { padding: var(--sp-3) 0; border-bottom: 1px solid var(--border); }
.activity-detail-row:last-child { border-bottom: none; }
.activity-detail-label {
  font-size: 0.6875rem; font-weight: 800; letter-spacing: .6px; text-transform: uppercase;
  color: var(--muted); margin-bottom: 4px;
}
.activity-detail-value { font-size: 0.875rem; color: var(--text-value); word-break: break-word; }

@media (max-width: 1023px) and (min-width: 768px) {
  .activity-summary-cards { grid-template-columns: repeat(2, 1fr); }
  .activity-details-drawer { width: 380px; }
}
@media (max-width: 767px) {
  .activity-summary-cards { grid-template-columns: repeat(2, 1fr); }
  .activity-details-drawer { width: 100vw; }
  .activity-pagination { flex-direction: column; align-items: stretch; text-align: center; }
}

/* ==================== Mobile top bar + drawer (hidden on desktop) ==================== */
.topbar { display: none; }
.nav-scrim { display: none; }

/* ============================================================================
   Responsive - DESKTOP-FIRST. The base rules above are the approved master
   design. These tiers ADAPT spacing / columns / touch targets - never
   redesign. Tiers: Large 1920+ | Small laptop 1024-1365 | Tablet 768-1023 |
   Mobile <768.

   2026-08-04 update (Desktop Responsive System spec, root-cause fix for
   real breakage reported on a smaller/DPI-scaled 15" laptop): the range
   1366-1919px used to be explicitly "used as-is" with zero adaptation -
   that gap is exactly where a scaled laptop's effective CSS viewport
   lands. .content's padding and .field's max-width are now fluid
   (clamp()-based, see their own rules above) across 1024-1920px instead
   of stepping at fixed breakpoints, so there's no longer an unadapted
   band. The tier rules below still hold for the properties that
   genuinely only make sense as discrete steps (grid column counts, touch
   targets) - 1920+/2560+ desktop behavior is unchanged throughout.
   ============================================================================ */

/* ---- Large desktop 1920+: more breathing room inside the centered max-width. */
@media (min-width: 1920px) {
  .content { padding: var(--sp-6) var(--sp-8); }
}

/* ---- Ultra-wide 2560+: the 1680px content cap (DESIGN_SYSTEM.md §13) already
   prevents stretching; this only adds extra side padding so the capped
   content doesn't sit flush against the outer edge on very wide monitors. */
@media (min-width: 2560px) {
  .content { padding-inline: var(--sp-10); }
}

/* ---- Small laptop 1024-1365: keep the desktop layout; trim spacing only.
   .content's own padding is no longer set here - the fluid clamp() on the
   base .content rule already covers this range continuously (and matches
   this tier's old 16/20px value exactly at 1024px, so nothing visually
   changes here, it just stops being a hard step). */
@media (max-width: 1365px) and (min-width: 1024px) {
  .deck-grid { gap: var(--sp-2); }
}

/* ---- Tablet 768-1023: compact desktop - stack form+preview, fewer deck
   columns, comfortable touch targets. No horizontal page scroll. */
@media (max-width: 1023px) and (min-width: 768px) {
  .content { padding: var(--sp-4); }
  .layout { grid-template-columns: 1fr; }            /* stack form + preview */
  .preview-box { min-height: 260px; }
  /* Command deck: 3 columns; let the home page scroll naturally. */
  #app-shell.home-active #page,
  #app-shell.home-active .layout.home { display: block; }
  #app-shell.home-active #page,
  #app-shell.home-active .layout.home,
  .layout.home .form { overflow: visible; }
  .layout.home .form, .home-deck { display: block; }
  .home-deck::before, .home-deck::after { display: none; }
  .deck-grid { grid-template-columns: repeat(3, 1fr); }
  #app-shell.home-active .deck-col:not(:last-child)::after { display: none; }
  /* Header brand scales down one step. */
  .cmd-logo { height: 64px; } /* orig 80px, -20% */
  .cmd-title { font-size: 1.5rem; } /* orig 1.75rem, -15% */
  .cmd-sub { font-size: 0.8125rem; }
  .cmd-controls { gap: 10px; } /* orig 12px, -15% */
  /* Login: narrower brand panel. */
  .login-left { flex-basis: 48%; padding: 2.5vh 4vw 0; }
  .login-hero-headline { font-size: 1.4625rem; }
  /* Touch: 44px minimum interactive targets (keeps the same visual style). */
  .btn, .cmd-btn, .userbar-btn, .seg-btn, .back-home, .btn-danger, .btn-mini,
  select, input[type=text], input[type=date] { min-height: 44px; }
}

/* ============================ Responsive (mobile <768) ============================ */
@media (max-width: 767px) {
  body { overflow: auto; }
  .app { flex-direction: column; height: auto; min-height: 100vh; }

  /* Mobile top bar removed by user request: the sidebar is hidden and the
     brand already sits in the top-user bar, so this redundant top strip is
     not shown. TO RESTORE: set display: flex. */
  .topbar { display: none !important; }

  /* Sidebar becomes a slide-in drawer */
  .rail {
    position: fixed; top: 0; left: 0; bottom: 0; width: 272px; z-index: var(--z-nav-drawer);
    transform: translateX(-100%); transition: transform .24s cubic-bezier(.4,0,.2,1);
    box-shadow: var(--sh-3);
  }
  .app.nav-open .rail { transform: translateX(0); }
  .nav-scrim { position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: var(--z-nav-scrim); }
  .app:not(.nav-open) .nav-scrim { display: none; }
  .app.nav-open .nav-scrim { display: block; animation: fade var(--t-fast) both; }

  /* Content stacks and scrolls naturally */
  .content { height: auto; overflow: visible; padding: var(--sp-3) var(--sp-4); }
  .page { height: auto; }
  .page-title { font-size: 1.125rem; }
  .layout { grid-template-columns: 1fr; }
  .form { overflow: visible; padding-right: 0; }
  .preview-box { height: 300px; }

  /* Compact command header so brand + controls never overlap on small screens. */
  .cmd-header { gap: var(--sp-2); padding: 6px 11px; flex-wrap: wrap; } /* vertical-only, additional -10% */
  .cmd-brand { gap: var(--sp-4); }
  .cmd-logo { height: 51px; } /* orig 64px, -20% */
  .cmd-sub { font-size: 0.75rem; letter-spacing: 0.18em; }
  .cmd-title { font-size: 1.25rem; } /* orig 1.5rem, -15% */
  .cmd-controls { gap: 9px; } /* orig 10px, -15% */
  .cmd-btn span { display: none; }               /* Home -> icon only */
  .cmd-btn { padding: 0; width: 44px; justify-content: center; }
  .userbar-name { font-size: 0.8rem; max-width: 120px; }

  /* Command deck: stack columns, let the page scroll naturally on mobile. */
  #app-shell.home-active #page,
  #app-shell.home-active .layout.home { display: block; }
  .layout.home .form { overflow: visible; display: block; }
  .home-deck { display: block; }
  .home-deck::before, .home-deck::after { display: none; }   /* fixed blobs off on scroll pages */
  .deck-grid { grid-template-columns: 1fr; }
  #app-shell.home-active .deck-col:not(:last-child)::after { display: none; }  /* stacked columns need no side divider */

  /* Touch: 44px minimum interactive targets (same as tablet; visual style
     unchanged - only the tap area grows). */
  .btn, .cmd-btn, .userbar-btn, .seg-btn, .back-home, .btn-danger, .btn-mini,
  select, input[type=text], input[type=date] { min-height: 44px; }

  /* Link Migration "get ..." command copy button: 22px is a fine mouse
     target but too tight to reliably tap - bump toward the 32px icon-button
     size already used elsewhere (e.g. .aea-row-del) without resizing the
     command bar itself. `.lm-cmd .lm-cmd-copy` (two classes) beats the
     later unconditional single-class `.lm-cmd-copy` rule on specificity. */
  .lm-cmd .lm-cmd-copy { width: 32px; height: 32px; }

  /* Segmented tab-switchers (Auto Configuration / TCU / Link Migration /
     BB RRU Creator / 5G Active mode toggles): the pill is shrink-to-fit
     with no wrap, so a long-label pair (e.g. "Active Configuration" / "DOT
     Deletion") can be wider than a phone screen. Let it wrap to two lines
     instead of overflowing. */
  .segmented { flex-wrap: wrap; }

  /* Inputs and pickers fill the row */
  .field { min-width: 0; max-width: none; flex: 1 1 150px; }
  .field select, .field input[type=text], .field input[type=date] { width: 100%; }
  .sec-checks { grid-template-columns: 1fr; }
  /* DUS Script General row: the fixed-width no-wrap layout (desktop/tablet,
     explicit user request) is wider than a phone screen - let it wrap here
     so Number of Sectors / Site ID / Site Name / the fingerprint toggle
     stack instead of overflowing the viewport. #form beats the later
     unconditional `.dus-general-row{flex-wrap:nowrap}` rule on specificity
     since both are single-class selectors otherwise. */
  #form .dus-general-row { flex-wrap: wrap; }
  /* Permission groups: single column on mobile. */
  .perm-grid, .modal-card .perm-grid { flex-direction: column; }
  .users-panel { max-width: none; }
  .preview-head select { width: 150px; }

  /* Modals / login fit small screens */
  .modal-card, .modal-card:has(.sec-checks), .modal-card:has(.perm-grid), .modal-card:has(.fs-dup-actions) { width: 92vw; max-width: 470px; }
  /* The duplicate-file dialog's 3 buttons can't all stay single-line on a
     phone-width modal without overflowing it - fall back to wrapping to a
     second row here (unlike desktop/tablet, which are wide enough not to). */
  .fs-dup-actions { flex-wrap: wrap; }
  /* Login: hide the brand panel, center the card full-width. Both themes
     keep showing the shared page photo (from .login-overlay) through
     .login-right; the darkening radius is just dialed down here so it stays
     a quiet backdrop, not a focal shape, on the small viewport. */
  .login-left { display: none; }
  .login-right {
    flex: 1 1 100%; padding: var(--sp-6);
    background: radial-gradient(380px 320px at 50% 38%, rgba(5,10,16,.35), transparent 60%);
  }
  :root[data-theme="light"] .login-right {
    background: radial-gradient(380px 320px at 50% 38%, rgba(20,30,35,.08), transparent 60%);
  }
  .login-card { width: 100%; max-width: 420px; padding: 32px 24px; }

  .actions .btn { flex: 1 1 auto; }
}

/* ---- Short-height viewports (phones in landscape, or any short window):
   width-based breakpoints above assume enough vertical room for the fixed
   100vh + internal-scroll layout; a phone turned sideways (e.g. 812x375)
   has plenty of width but very little height, so .form/.preview-panel were
   squeezed to a sliver. Independent of width, let the page scroll naturally
   instead - same strategy as the <768px block above. */
@media (max-height: 540px) {
  body { overflow: auto; }
  .app { height: auto; min-height: 100vh; }
  .content { height: auto; overflow: visible; }
  .page { height: auto; }
  .layout { grid-template-columns: 1fr; }
  .form { overflow: visible; padding-right: 0; }
  .preview-box { height: 260px; }
  #app-shell.home-active #page,
  #app-shell.home-active .layout.home { display: block; }
  .layout.home .form { overflow: visible; display: block; }
  .home-deck { display: block; }
  .home-deck::before, .home-deck::after { display: none; }
  #app-shell.home-active .deck-col:not(:last-child)::after { display: none; }
}

/* ============================ Keyframes ============================ */
@keyframes fade-up { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes pop-in  { from { opacity: 0; transform: translateY(6px) scale(.985); } to { opacity: 1; transform: none; } }
@keyframes fade    { from { opacity: 0; } to { opacity: 1; } }
@keyframes pulse-glow {
  0%, 100% { opacity: 1; }
  50%      { opacity: .55; }
}
@keyframes mesh-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-2%, 2%, 0) scale(1.06); }
}

/* Respect users who prefer reduced motion (accessibility). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
}

/* ============================================================================
   Radio Topology Builder (Manual mode of Baseband RRU Creation).
   Visual engineering workspace: left = sectors/radios, right = sticky summary.
   ========================================================================== */
.tb-wrap { display: grid; grid-template-columns: minmax(0,1fr) 300px; gap: var(--sp-4); align-items: start; }
.tb-workspace { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-4); }
.tb-summary {
  position: sticky; top: var(--sp-4); align-self: start;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4); box-shadow: var(--sh-1);
}
.tb-sum-title { font-size: var(--fs-13, 13px); font-weight: 700; letter-spacing: var(--tracking-caps);
  text-transform: uppercase; color: var(--text-secondary); margin-bottom: var(--sp-3); }
.tb-sum-row { display: flex; justify-content: space-between; align-items: center;
  padding: 6px 0; border-bottom: 1px solid var(--border); }
.tb-sum-row:last-of-type { border-bottom: 0; }
.tb-sum-k { color: var(--text-2); font-size: 0.8125rem; }
.tb-sum-v { color: var(--text-value); font-weight: 700; font-variant-numeric: tabular-nums; }
.tb-val { margin-top: var(--sp-3); padding: 8px 12px; border-radius: var(--r-md);
  font-size: 0.8125rem; font-weight: 600; text-align: center; }
.tb-val.ok  { background: rgba(47,174,106,.14); color: var(--ok); border: 1px solid rgba(47,174,106,.4); }
.tb-val.err { background: var(--error-soft); color: var(--error); border: 1px solid rgba(225,91,82,.4); }
/* Per-sector breakdown (2026-08-03, 5G active Advance's Topology Summary -
   explicit user correction: show each sector's own counts, not one combined
   total) - reuses .tb-sum-row/-k/-v as-is inside a dashed-divider block per
   sector, same panel BB RRU Creator's own (non-per-sector) summary uses. */
.tb-sum-sector { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--border); }
.tb-sum-sector-title { font-size: 0.8125rem; font-weight: 700; color: var(--text-value); margin-bottom: 2px; }

.tb-sector { }
.tb-sector-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-2); }
.tb-del { padding: 4px 12px; font-size: 0.75rem; }
.tb-block { margin-top: var(--sp-3); }
.tb-block-lbl { font-size: 0.75rem; font-weight: 700; letter-spacing: var(--tracking-caps);
  text-transform: uppercase; color: var(--muted); margin-bottom: var(--sp-2); }
.tb-empty { color: var(--muted); font-size: 0.8125rem; font-style: italic; padding: var(--sp-2) 0; }
.tb-radio-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.tb-radio { flex: 1 1 320px; min-width: 0; background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-3); }
.tb-radio-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-2); }
.tb-radio-name { font-weight: 700; color: var(--accent); font-family: var(--mono, monospace); font-size: 0.8125rem; }
.tb-x { width: 22px; height: 22px; border-radius: var(--r-sm); border: 1px solid var(--border);
  background: transparent; color: var(--muted); cursor: pointer; line-height: 1; }
.tb-x:hover { border-color: var(--error); color: var(--error); }
.tb-add, .tb-add-sector { margin-top: var(--sp-3); padding: 7px 14px; font-size: 0.8125rem; }
.tb-add[disabled] { opacity: .45; cursor: not-allowed; }

.tb-conn { margin-top: var(--sp-3); }
.tb-conn-lbl { font-size: 0.6875rem; font-weight: 700; letter-spacing: var(--tracking-caps);
  text-transform: uppercase; color: var(--muted); margin-right: var(--sp-2); }
.tb-chain { display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-top: 6px; }
.tb-node { padding: 4px 10px; border-radius: var(--r-pill); font-size: 0.75rem; font-weight: 600;
  background: var(--accent-soft); border: 1px solid var(--accent); color: var(--text-value); }
.tb-node.bb { background: var(--panel2); border-color: var(--border); color: var(--text-2); }
.tb-link { color: var(--muted); font-weight: 700; }

/* Threshold widened 1024px -> 1440px (2026-08-04, Desktop Responsive System
   spec): at 1366px the fixed 300px summary column left too little room for
   the nested workspace content (measured on 5G Active Advance - the
   Multicast Antenna Branch "Transceiver (This Branch)" label was truncating
   even though it's built to degrade gracefully via ellipsis, i.e. the row
   was simply too starved of width, not a broken grid). Stacking the summary
   below the workspace earlier gives the workspace the full column width
   through the whole 1366-1439px "primary laptop" band. Shared by BB RRU
   Creator too - same benefit there, no downside (its own content is
   simpler key/value rows). Untouched at 1440px+, where there's already
   enough room for the side-by-side layout. */
@media (max-width: 1440px) {
  .tb-wrap { grid-template-columns: 1fr; }
  .tb-summary { position: static; }
}

/* ---- Topology Builder: groups, branch mapping, sector functions ---- */
.tb-group-row, .tb-sef-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.tb-group, .tb-sef { flex: 1 1 260px; min-width: 0; background: var(--panel2);
  border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-3); }
.tb-group-head, .tb-sef-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: var(--sp-2); min-height: 22px; }
.tb-group-name { font-weight: 700; color: var(--text-heading); font-size: 0.8125rem; }
/* Fixed "Group" prefix + editable value: keeps the label stable in both modes. */
.tb-group-name-wrap { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
/* Inline rename: compact, proportional to the title -- never full-width. */
.tb-group-acts { display: inline-flex; align-items: center; gap: 6px; flex: none; }
.tb-group-name-input { width: 74px; max-width: 50%; font-weight: 700; font-size: 0.8125rem;
  color: var(--text-heading); background: var(--panel); border: 1px solid var(--accent);
  border-radius: var(--r-sm); padding: 1px 6px; line-height: 1.4; }
.tb-group-name-input:focus { outline: none; box-shadow: var(--ring); }
.tb-gedit { width: 22px; height: 22px; border-radius: var(--r-sm); border: 1px solid var(--border);
  background: transparent; color: var(--muted); cursor: pointer; line-height: 1; padding: 0;
  display: inline-flex; align-items: center; justify-content: center; }
.tb-gedit:hover { border-color: var(--accent); color: var(--accent); }
.tb-gedit .ic { width: 13px; height: 13px; }
.tb-branch { display: flex; align-items: center; gap: var(--sp-2); margin: 6px 0; flex-wrap: wrap; }
.tb-branch-tag { font-size: 0.75rem; font-weight: 700; color: var(--accent);
  background: var(--accent-soft); border: 1px solid var(--accent); border-radius: var(--r-sm); padding: 2px 8px; white-space: nowrap; }
.tb-map { font-size: 0.6875rem; color: var(--muted); font-weight: 600; white-space: nowrap; }
.tb-port-sel { flex: 1 1 120px; min-width: 100px; }
.tb-add.sm { padding: 4px 10px; font-size: 0.75rem; margin-top: 6px; }
.tb-avail { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-bottom: var(--sp-2); }
.tb-avail-lbl { font-size: 0.75rem; color: var(--muted); font-weight: 600; margin-right: var(--sp-1); }
.tb-chip { display: inline-flex; align-items: center; gap: var(--sp-1); font-size: 0.75rem; font-weight: 600;
  padding: 3px 10px; border-radius: var(--r-pill); cursor: pointer;
  background: var(--accent-soft); border: 1px solid var(--accent); color: var(--text-value); }
.tb-chip.muted { background: var(--panel); border-color: var(--border); color: var(--text-2); cursor: default; }
.tb-chip:hover:not(.muted) { border-color: var(--error); color: var(--error); }
.tb-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--sp-2); min-height: 24px; }
.tb-sef-name { margin: 0; flex: 1 1 auto; }
.tb-sef-name label { display: none; }
.tb-assign { width: 100%; }
.tb-val-more { text-align: center; font-size: 0.6875rem; color: var(--muted); margin-top: var(--sp-1); }

/* ============================================================================
   Topology Builder -- UI cleanup pass (visual consistency, KAREEM TEQ tokens).
   Structure/logic unchanged; this only refines hierarchy, spacing and labels.
   ========================================================================== */
/* small variant of the existing button component (no new colors) */
.btn.sm { padding: 6px 12px; font-size: 0.78125rem; }

/* engineering-module section header: title left, action right, divider under */
.tb-block { margin-top: var(--sp-5); }
.tb-block:first-of-type { margin-top: var(--sp-3); }
.tb-block-head { display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); padding-bottom: var(--sp-2); margin-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border); }
.tb-block-title { font-size: 0.75rem; font-weight: 700; letter-spacing: var(--tracking-caps);
  text-transform: uppercase; color: var(--text-secondary); }

/* two-line professional empty state */
.tb-empty { color: var(--muted); font-size: 0.8125rem; font-style: normal; padding: var(--sp-3) 0; }
.tb-empty-1 { color: var(--text-2); font-weight: 600; }
.tb-empty-2 { color: var(--muted); font-size: 0.78125rem; margin-top: 2px; }

/* sector card: clearer header + even padding */
.tb-sector { padding: var(--sp-4); }
.tb-sector-head { padding-bottom: var(--sp-3); margin-bottom: var(--sp-2); border-bottom: 1px solid var(--border-2); }
.tb-sector-head .card-title { margin: 0; }
.tb-add-sector { margin-top: var(--sp-2); align-self: flex-start; }

/* available-branches strip */
.tb-avail-none { font-size: 0.75rem; color: var(--muted); font-style: italic; }

/* validation presentation: glyph + tidy typography */
.tb-val { display: flex; align-items: center; justify-content: center; gap: 6px; line-height: 1.35; text-align: left; }
.tb-val.ok::before  { content: "\2713"; font-weight: 800; }
.tb-val.err::before { content: "\26A0"; font-weight: 800; }
.tb-val-more { text-align: center; }

/* keep the two-column workspace; collapse cleanly on tablet/mobile */
@media (max-width: 768px) {
  .tb-radio, .tb-group, .tb-sef { flex: 1 1 100%; }
  .tb-block-head { flex-wrap: wrap; }
}

/* ---- Topology Builder: Cascade DATA-port auto-assignment display ---- */
.tb-data-auto { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 9px 12px; font-size: 0.8125rem; font-weight: 600; color: var(--text-2); font-family: var(--mono, monospace); }
.tb-data-tag { font-size: 0.6875rem; font-weight: 700; color: var(--muted); white-space: nowrap;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 1px 6px; }

/* #page-toolbar (.ip-toolbar-slot) is a full-width slot between the page
   header and the form/preview split (NOT inside the form column), used by
   5G Active/DOT Deletion's tab switcher and DOT Deletion's upload status
   card. (The IP Plan import toolbar this slot/class was originally named
   for was removed 2026-07-28 - it was a prototype behind `toolbar: true`
   that no SECTIONS entry ever actually set, so it never rendered.) */
.ip-toolbar-slot:empty { display: none; }
.ip-toolbar-slot { margin-bottom: var(--sp-4); }

/* ============================================================================
   Router Configuration Generator — Enterprise UI Redesign
   Scoped with rtr- prefix. No global-scope changes.
   ========================================================================== */

/* ---- Page breathing room (header -> content) ---- */
.rtr-spacer { height: 6px; }

/* ---- Technology selector: one continuous segmented control ----
   Four equal segments inside a single container, split by hairline dividers
   rather than four detached cards. Total width stays at the previous 4x110px
   so the control still reads as a deliberate group, not a full-width strip. */
.rtr-tech-row {
  display: flex; gap: 0; margin-bottom: var(--sp-4);
  width: 440px; max-width: 100%;
  background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--r-md);
  overflow: hidden;
}
.rtr-tech-card {
  flex: 1 1 0; min-width: 0; cursor: pointer; user-select: none;
  display: flex; align-items: center; justify-content: center;
  min-height: var(--control-h); padding: 0 12px; line-height: 1;
  background: transparent; border: 0; border-radius: 0;
  /* Every segment carries the divider box (the first one's is invisible) so all
     four end up exactly the same width. */
  border-left: 1px solid transparent;
  font-weight: 500; font-size: 0.78125rem; letter-spacing: .6px; color: var(--text-2);
  transition: background var(--t), color var(--t), box-shadow var(--t);
}
.rtr-tech-card + .rtr-tech-card { border-left-color: var(--border); }
.rtr-tech-card:hover { background: var(--surface-4); color: var(--text); }
/* Active: inset only, so the container's overflow clip can't cut the glow. */
.rtr-tech-card.active {
  background: var(--accent-soft); color: var(--accent); font-weight: 700;
  box-shadow: inset 0 0 0 1px var(--accent), inset 0 0 12px rgba(22,163,148,.12);
}

/* ---- Animated expand/collapse config sections ---- */
.rtr-section {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows 350ms var(--ease-out), opacity 250ms var(--ease-out),
              margin-top 350ms var(--ease-out);
  opacity: 0;
}
.rtr-section > .rtr-inner { overflow: hidden; }
.rtr-section.open { grid-template-rows: 1fr; opacity: 1; }
/* Slightly lighter section-title weight for the 2G/3G/LTE/5G config cards
   (Technology Cards above already carry the identity, no need to repeat it). */
.rtr-section .card-title { font-weight: 500; color: var(--text-2); }

/* ---- Site Configuration: every control on one line ----
   Site ID / Connection Type / Main Link Port live in one wrapper and the
   Diversity pair in a second one, so the pair used to start its own row. The
   CARD is the grid instead, and both wrappers dissolve into it via
   display:contents - all four controls then share a single row.
     Single    -> Site ID | Connection Type | Main Link Port | (col 4 empty)
     Diversity -> Site ID | Connection Type | Primary Port   | Secondary Port
   .hidden is display:none !important, so a hidden wrapper/field drops out of the
   grid entirely and the next control slides up into its column. */
.rtr-site-grid { display: contents; }
.rtr-site-grid .field { max-width: none; min-width: 0; width: 100%; }

/* Sized to their content rather than stretched across the card: the two text
   fields match each other, the two port pickers match each other, and the
   trailing 1fr track just soaks up the leftover width (it also lets the title's
   divider span the full card). */
#form:has(.rtr-tech-row) .card:has(> .rtr-site-grid) {
  display: grid;
  grid-template-columns: 190px 190px 116px 116px minmax(0, 1fr);
  gap: 9px 16px; align-items: start;
}
#form:has(.rtr-tech-row) .card:has(> .rtr-site-grid) > .card-title {
  grid-column: 1 / -1; margin-bottom: 0;
}

/* A port picker holds a one- or two-digit number: 116px, matching the port
   pickers inside the technology cards (and staying narrow once the grid
   collapses to a single column on tablet). */
.rtr-site-grid > .field:nth-child(3),
.rtr-site-grid + .rtr-site-grid > .field { width: 116px; max-width: 116px; }

/* 2026-08-04, Desktop Responsive System spec: the 4 fixed columns above
   (190+190+116+116px + gaps = 676px minimum) measured a real ~28px overflow
   (absorbed silently by .form's own overflow-x:auto, not a page-level
   scrollbar, but still real clipping/overflow) starting around ~1200px
   effective viewport. Reuses the same 2-column wrap the existing ≤768px
   rule already applies, just at a wider threshold - same fix pattern as
   .t4g-general-grid above. */
@media (max-width: 1280px) {
  #form:has(.rtr-tech-row) .card:has(> .rtr-site-grid) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ---- Card spacing (balanced whitespace between major sections) ----
   A collapsed section is zero-height but used to keep its 16px margin, so four
   disabled technologies stacked 64px of dead space above Router Configuration.
   Only an open section reserves its gap; the closing card keeps a single one. */
.rtr-spacer ~ .card { margin-top: 0; }
.rtr-section.open { margin-top: var(--sp-4); }
.rtr-section + .card { margin-top: var(--sp-4); }

/* ---- Port allocation area inside cards ---- */
.rtr-ports-area { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--border); }
.rtr-ports-lbl {
  font-size: 0.6875rem; font-weight: 700; letter-spacing: var(--tracking-caps);
  text-transform: uppercase; color: var(--text-secondary); margin-bottom: var(--sp-2);
}
.rtr-ports-row { display: flex; flex-wrap: wrap; gap: 10px 14px; }
.rtr-ports-row .field { min-width: 96px; max-width: 116px; flex: 0 1 116px; }

/* ---- Bridge name: modern toggle switch + expand ---- */
.rtr-bridge { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--border); }
.rtr-toggle-row { display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
.rtr-toggle-row input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
/* Restyled 2026-07-28 to match the global input[type=checkbox] toggle
   switch pixel-for-pixel (same colors/transition) - user-approved
   unification of the two different switch styles in the app (this custom
   track+thumb build vs. the plain styled-checkbox toggle used everywhere
   else, e.g. .check-row). Both scaled -10% together again on 2026-08-04
   (explicit app-wide request) so they stay pixel-identical.
   Kept as its own hidden-input+track+thumb markup (main.js unchanged, see
   the "hidden input" comment above) since Router's toggle needs a label
   that isn't just adjacent text. */
.rtr-toggle-track {
  position: relative; flex-shrink: 0; width: 34.2px; height: 19.8px; /* -10%, app-wide */
  border-radius: var(--r-pill); background: var(--switch-off); border: none;
  transition: background 190ms var(--ease-out);
}
.rtr-toggle-thumb {
  position: absolute; top: 1.8px; left: 1.8px; width: 16.2px; height: 16.2px;
  border-radius: 50%; background: var(--switch-knob); box-shadow: 0 1px 2px rgba(0,0,0,.35);
  transition: transform 190ms var(--ease-out);
}
.rtr-toggle-row input:checked + .rtr-toggle-track { background: var(--accent); }
.rtr-toggle-row input:checked + .rtr-toggle-track .rtr-toggle-thumb { transform: translateX(14.4px); }
.rtr-toggle-lbl { font-size: 0.75rem; font-weight: 500; color: var(--text-2); }
.rtr-bridge-body {
  display: grid; grid-template-rows: minmax(0, 0fr);
  transition: grid-template-rows 300ms var(--ease-out), opacity 200ms var(--ease-out);
  opacity: 0;
}
.rtr-bridge-body > div { overflow: hidden; }
.rtr-bridge-body.open { grid-template-rows: minmax(0, 1fr); opacity: 1; }

/* ---- "Generated Configuration" panel title (Router page only; matches
   the 13px/600 card-title sizing instead of the shared uppercase "Preview" style) ---- */
.preview-head .ttl.rtr-ttl {
  font-weight: 700; font-size: 0.8125rem; letter-spacing: .2px;
  text-transform: none; color: var(--text-heading);
}

/* ---- Responsive ---- */
@media (max-width: 768px) {
  /* The four labels are short, so the control stays one unbroken row and simply
     spans the column instead of wrapping into a 2x2 block. */
  .rtr-tech-row { width: 100%; }
  /* Two columns rather than a full stack: Site ID | Connection Type, then the
     uplink ports stay paired on the row below instead of sitting one per line. */
  #form:has(.rtr-tech-row) .card:has(> .rtr-site-grid) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .rtr-ports-row .field { min-width: 80px; flex: 1 1 80px; }
}

/* ============================================================================
   Router page ONLY: refinements to SHARED components (.card, select, preview
   placeholder), scoped via :has(.rtr-tech-row) / :has(.rtr-ttl) so no other
   page is touched. Pure visual polish - no structural or logic change.
   ========================================================================== */

/* Cards: one radius / border / padding / shadow across every card on the page,
   denser than the shared default and with a hairline top highlight for depth
   (weight unchanged - the highlight is an inset, not a heavier shadow). */
#form:has(.rtr-tech-row) > .card,
#form:has(.rtr-tech-row) .rtr-section .card {
  padding: 10px 14px 12px;
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1), inset 0 1px 0 rgba(255,255,255,.022);
}
#form:has(.rtr-tech-row) .card > .card-title {
  margin-bottom: 9px; padding-bottom: var(--sp-2);
}

/* Inputs: a touch taller with the text optically centred, identical metrics for
   every <input> and <select> on the page. */
#form:has(.rtr-tech-row) .field { gap: var(--field-gap-lg); }
#form:has(.rtr-tech-row) select,
#form:has(.rtr-tech-row) input[type=text] {
  padding: 9px 11px; line-height: 1.25; border-radius: var(--r-md);
}

/* Dropdowns: custom chevron, optically centred against the taller field
   (accent chevron on hover/focus). */
#form:has(.rtr-tech-row) select {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  padding-right: var(--sp-8);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239FB0C2' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center; background-size: 12px 12px;
}
#form:has(.rtr-tech-row) select:hover,
#form:has(.rtr-tech-row) select:focus {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2316a394' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}

/* Item 1: tighter Router title -> subtitle gap only. The page-head bottom
   margin used to be reduced here too (8px vs the shared 16px everywhere
   else), but that margin also governs the Action Toolbar's gap to the
   content below it -- so Router's toolbar sat 10px closer to the form/preview
   panels than on every other page. Restored to the shared value so the
   Action Toolbar's position is pixel-identical across all pages. */
.content:has(.rtr-tech-row) .page-sub { margin-top: 2px; }
/* The tighter title->subtitle gap above makes the title block (and so
   .page-head, which sizes to its tallest child) 2px shorter than on every
   other page, which nudges the Action Toolbar -- vertically centered in that
   row -- up by 1px and shifts everything below by 2px. Restoring the row's
   height (not the typography) keeps the toolbar pixel-identical everywhere
   while leaving the tighter title/subtitle spacing untouched. 58px is the
   shared .page-head height measured on every non-Router page. */
.content:has(.rtr-tech-row) .page-head { min-height: 58px; }

/* Item 11: barely-there top gradient for depth. Palette untouched - this only
   lifts the first ~340px by ~1.5% luminance over the existing background. */
.content:has(.rtr-tech-row) {
  background-image: linear-gradient(180deg, rgba(255,255,255,.015), rgba(255,255,255,0) 340px);
}

/* Router: both downloads read as equal secondary actions;
   Generate stays the only filled button. */
.content:has(.rtr-tech-row) .actions .btn.ok {
  background: transparent; color: var(--text);
  border-color: var(--border); box-shadow: none;
}
.content:has(.rtr-tech-row) .actions .btn.ok:hover {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent);
}

/* Router preview: header alignment + empty state (keeps own visual identity) */
.preview-panel:has(.rtr-ttl) { border-color: var(--border); }
.preview-panel:has(.rtr-ttl) .preview-head { align-items: center; margin-bottom: 10px; }
.preview-panel:has(.rtr-ttl) .preview-head select { padding: 9px 11px; line-height: 1.25; }
.preview-panel:has(.rtr-ttl) #preview.is-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  height: 100%; text-align: center; padding-bottom: 30px;
  font-family: var(--font); font-size: 0.8125rem; font-weight: 500;
  color: var(--text-placeholder); letter-spacing: .2px; white-space: normal;
}
.preview-panel:has(.rtr-ttl) #preview.is-empty::before {
  content: ""; display: block; width: 46px; height: 46px; margin-bottom: 14px; opacity: .4;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748B' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'/%3E%3Cpolyline points='14 2 14 8 20 8'/%3E%3Cline x1='8' y1='13' x2='16' y2='13'/%3E%3Cline x1='8' y1='17' x2='13' y2='17'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* Enterprise visual redesign removed — all pages now use the shared base
   preview panel style (matching Router proportions and spacing). */

/* Always present: the row reserves its height whether or not a file exists, so
   the code below never shifts. It blends into the panel (transparent fill, no
   colored banner); only the icon + text carry meaning. */
.preview-status {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 38px; padding: 0 14px;
  font-family: var(--font); font-size: 0.75rem; font-weight: 500; letter-spacing: .2px;
  border-bottom: 1px solid var(--preview-divider);
  background: transparent; color: var(--text-2);
}
.preview-status:empty { display: none; }
.preview-status::before {
  content: ""; flex: none; width: 15px; height: 15px; display: none;
  background: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
}
.preview-status.ok::before {
  display: block;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
}
.preview-status.err { color: var(--error-2); }
.preview-status.err::before {
  display: block;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cline x1='12' y1='8' x2='12' y2='12'/%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cline x1='12' y1='8' x2='12' y2='12'/%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'/%3E%3C/svg%3E");
}
.preview-status.idle { color: var(--warn); }
.preview-status.idle::before {
  display: block; width: 8px; height: 8px; border-radius: 50%;
}
.preview-status.busy { color: var(--accent); }
.preview-status.busy::before {
  display: block; width: 14px; height: 14px;
  background: none;
  border: 2px solid var(--preview-divider);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: preview-spin .8s linear infinite;
}
@keyframes preview-spin { to { transform: rotate(360deg); } }
.preview-status.ok { color: #5a9e7c; }

/* --- Mobile: 44px title bar / touch-friendly */
@media (max-width: 767px) {
  .preview-head { min-height: 44px; }
  .preview-box { padding: 12px 14px; }
}

/* ============================================================================
   One Click IP - Phase 2 workspace (UI only)
   Sections: upload zone / validation pipeline / output preview table. Uses
   only the existing design tokens (colors, spacing, radius, shadows, motion).
   ============================================================================ */
.ocip-workspace {
  display: flex; flex-direction: column; gap: var(--sp-5);
  max-width: 1280px; margin: 0 auto; padding-bottom: var(--sp-6);
}

/* ---- Section 1: Import Data (Browse File) ---- */
.ocip-import {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-6);
  transition: border-color var(--t-fast), background var(--t-fast);
}
.ocip-file-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
/* Drag & drop feedback (added 2026-07-28 to match DOT Deletion's upload
   card - user-requested unification): whole section accepts a drop,
   whether empty or already showing a loaded file. */
.ocip-import.drag-hover {
  border-color: var(--accent); border-style: dashed;
  background: var(--accent-soft);
}

/* Empty state - the import button */
.ocip-import-empty {
  display: flex; flex-direction: column; gap: var(--sp-4);
}
.ocip-import-title {
  font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: .8px;
  color: var(--muted); text-align: center;
}
.ocip-import-btns {
  display: grid; grid-template-columns: 1fr; gap: var(--sp-4);
}
.ocip-import-card {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-2);
  background: var(--surface-2, var(--panel));
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-6) var(--sp-4);
  min-height: 160px;
  cursor: pointer;
  color: var(--text);
  transition: border-color var(--t-fast), background var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
  text-align: center;
}
.ocip-import-card:hover {
  border-color: var(--accent);
  background: var(--surface-4, var(--panel2, var(--panel)));
  transform: translateY(-1px);
  box-shadow: var(--sh-md);
}
.ocip-import-card:focus-visible { outline: none; box-shadow: var(--ring, 0 0 0 3px rgba(22,163,148,.18)); }
.ocip-import-card-ic {
  width: 52px; height: 52px; border-radius: var(--r-lg);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent);
  margin-bottom: var(--sp-1);
}
.ocip-import-card-ic .ic { width: 28px; height: 28px; }
.ocip-import-card-label {
  font-size: 0.9375rem; font-weight: 600; color: var(--text);
}
.ocip-import-card-hint {
  font-size: 0.75rem; color: var(--muted); line-height: 1.5;
}

/* ---- Loaded / rejected state: enterprise "Active Input" card ---- */
.ocip-active-card { display: none; flex-direction: column; width: 100%; }
.ocip-import.has-file .ocip-import-empty { display: none; }
.ocip-import.has-file .ocip-active-card { display: flex; }
.ocip-import.has-file { padding: var(--sp-5); }

/* Head: two aligned group titles */
.ocip-ac-head {
  display: flex; align-items: center; justify-content: space-between;
  padding-bottom: var(--sp-2); border-bottom: 1px solid var(--border);
}
.ocip-ac-title {
  font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: .8px;
  color: var(--muted);
}

/* Body: file info (left) | status (right) */
.ocip-ac-body {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  padding: var(--sp-3) 0;
}
.ocip-ac-file { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.ocip-loaded-ic {
  width: 46px; height: 46px; flex: none; border-radius: var(--r-md);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent);
}
.ocip-loaded-ic .ic { width: 24px; height: 24px; }
.ocip-loaded-meta { min-width: 0; }
.ocip-loaded-name {
  color: var(--text); font-weight: 700; font-size: 0.9375rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ocip-loaded-type { color: var(--text-2); font-size: 0.75rem; margin-top: 3px; }
.ocip-loaded-size { color: var(--muted); font-size: 0.75rem; margin-top: 1px; }

/* Status group (right-aligned) */
.ocip-ac-status {
  display: flex; flex-direction: column; align-items: flex-end; gap: 3px;
  flex: none; text-align: right;
}
.ocip-ac-badge {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: .6px;
  color: var(--muted);
}
.ocip-ac-badge::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none;
}
.ocip-ac-msg { color: var(--text); font-size: 0.75rem; }
.ocip-ac-status.ok  .ocip-ac-badge { color: var(--ok); }
.ocip-ac-status.ok  .ocip-ac-badge::before { background: var(--ok); }
.ocip-ac-status.err .ocip-ac-badge { color: var(--error); }
.ocip-ac-status.err .ocip-ac-badge::before { background: var(--error); }
.ocip-ac-status.busy .ocip-ac-badge { color: var(--accent); }
.ocip-ac-status.busy .ocip-ac-badge::before { background: var(--accent); animation: ocip-pulse 1s ease-in-out infinite; }
@keyframes ocip-pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

/* Actions: equal-width, horizontally aligned */
.ocip-ac-actions {
  display: flex; gap: var(--sp-3); padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
}
.ocip-ac-actions .btn { flex: 1 1 0; justify-content: center; }

/* ---- Section 2: Validation pipeline ---- */
.ocip-pipeline {
  display: grid; grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr;
  align-items: flex-start; gap: 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-5) var(--sp-6);
  box-shadow: var(--sh-1);
}
.ocip-step {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  text-align: center; min-width: 0;
}
.ocip-step-conn {
  height: 2px; margin-top: 18px; align-self: flex-start;
  background: var(--border); border-radius: 2px;
  min-width: 24px; flex: 1 1 auto;
  transition: background var(--t-med);
}
.ocip-step-badge {
  position: relative;
  width: 38px; height: 38px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel2); border: 1.5px solid var(--border-2); color: var(--text-2);
  font-weight: 700; font-size: 0.875rem;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast), box-shadow var(--t-fast);
}
.ocip-step-num { display: inline; }
.ocip-step-ic { display: none; position: absolute; inset: 0; align-items: center; justify-content: center; }
.ocip-step-ic .ic { width: 18px; height: 18px; }
.ocip-step-body { min-width: 0; }
.ocip-step-label { font-size: 0.8125rem; font-weight: 600; color: var(--text); }
.ocip-step-status { font-size: 0.6875rem; color: var(--muted); margin-top: 2px; text-transform: uppercase; letter-spacing: .6px; }
.ocip-step-err { display: none; font-size: 0.71875rem; color: var(--error); margin-top: var(--sp-1); max-width: 220px; }
/* Waiting (default): already styled above; slight dim */
.ocip-step.waiting { opacity: .78; }
/* Checking */
.ocip-step.checking .ocip-step-num { display: none; }
.ocip-step.checking .ocip-step-ic--spin { display: inline-flex; }
.ocip-step.checking .ocip-step-ic--spin .ic { animation: ocip-spin .9s linear infinite; }
.ocip-step.checking .ocip-step-badge {
  border-color: var(--accent); color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 4px rgba(22,163,148,.14);
}
.ocip-step.checking .ocip-step-status { color: var(--accent); }
/* Passed */
.ocip-step.passed .ocip-step-num { display: none; }
.ocip-step.passed .ocip-step-ic--check { display: inline-flex; }
.ocip-step.passed .ocip-step-badge {
  background: var(--ok); border-color: var(--ok); color: #fff;
}
.ocip-step.passed .ocip-step-status { color: var(--ok); }
/* Failed */
.ocip-step.failed .ocip-step-num { display: none; }
.ocip-step.failed .ocip-step-ic--x { display: inline-flex; }
.ocip-step.failed .ocip-step-badge {
  background: var(--error); border-color: var(--error); color: #fff;
}
.ocip-step.failed .ocip-step-status { color: var(--error); }
.ocip-step.failed .ocip-step-err { display: block; }
/* Connector color follows the LEFT step's state (approx: passed lights it) */
.ocip-step.passed + .ocip-step-conn { background: var(--ok); }
.ocip-step.failed + .ocip-step-conn { background: var(--error); }
@keyframes ocip-spin { to { transform: rotate(360deg); } }

/* ---- Section 3: Output preview table ---- */
.ocip-preview {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  display: flex; flex-direction: column; min-height: 0;
  overflow: hidden;
}
.ocip-preview-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
}
.ocip-preview-ttl { display: inline-flex; align-items: center; gap: var(--sp-2); font-weight: 700; font-size: 0.75rem; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); }
.ocip-preview-ttl .ic { width: 15px; height: 15px; color: var(--accent); }
.ocip-preview-meta { font-size: 0.75rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.ocip-table-scroll {
  position: relative; overflow: auto; max-height: 480px; min-height: 240px;
  background: var(--panel);
}
.ocip-table {
  width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 0;
  font-family: var(--font); font-size: 0.6875rem; color: var(--text);
}
.ocip-table thead th {
  position: sticky; top: 0; z-index: var(--z-raise-2);
  text-align: left; font-weight: 700; font-size: 0.59375rem; letter-spacing: .3px;
  text-transform: uppercase; color: var(--text-2);
  background: var(--panel2);
  padding: 6px 6px;
  border-bottom: 1px solid var(--border);
  white-space: normal; word-break: break-word;
}
.ocip-table tbody td {
  padding: 6px 6px;
  border-bottom: 1px solid var(--border);
  border-right: 1px solid var(--border);
  white-space: normal; word-break: break-word;
  color: var(--text);
}
.ocip-table tbody td:last-child { border-right: 0; }
.ocip-table thead th + th { border-left: 1px solid var(--border); }
.ocip-table tbody tr:hover td { background: rgba(255,255,255,.03); }
.ocip-table tbody tr.ocip-skeleton td {
  padding: 12px 14px;
}
.ocip-table tbody tr.ocip-skeleton td::before {
  content: ""; display: block; height: 10px; border-radius: 4px;
  background: linear-gradient(90deg, var(--panel2) 0%, var(--surface-4) 50%, var(--panel2) 100%);
  background-size: 200% 100%;
  animation: ocip-shimmer 1.2s ease-in-out infinite;
}
@keyframes ocip-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
/* Empty-state overlay in the scroll area (default until Phase 3 populates rows) */
.ocip-table-empty {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center;
  padding: var(--sp-6); gap: var(--sp-2);
  color: var(--muted); pointer-events: none;
}
.ocip-preview.has-rows .ocip-table-empty,
.ocip-preview.loading .ocip-table-empty { display: none; }
.ocip-table-empty-ic {
  width: 48px; height: 48px; border-radius: var(--r-md);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel2); color: var(--text-2);
}
.ocip-table-empty-ic .ic { width: 24px; height: 24px; }
.ocip-table-empty-ttl { font-size: 0.875rem; font-weight: 600; color: var(--text-2); margin-top: var(--sp-1); }
.ocip-table-empty-sub { font-size: 0.78125rem; color: var(--muted); max-width: 360px; line-height: 1.5; }

/* ---- Responsive ---- */
@media (max-width: 1024px) {
  .ocip-pipeline {
    grid-template-columns: 1fr 1fr; row-gap: var(--sp-4); column-gap: var(--sp-4);
    padding: var(--sp-4);
  }
  .ocip-step-conn { display: none; }
}
@media (max-width: 640px) {
  .ocip-workspace { gap: var(--sp-4); }
  .ocip-import { padding: var(--sp-4); }
  .ocip-import-card { min-height: 130px; padding: var(--sp-4) var(--sp-3); }
  .ocip-import-card-ic { width: 44px; height: 44px; }
  .ocip-import-card-ic .ic { width: 22px; height: 22px; }
  .ocip-import-card-label { font-size: 0.875rem; }
  .ocip-pipeline { grid-template-columns: 1fr; }
  .ocip-preview-head { padding: var(--sp-2) var(--sp-3); }
}

/* Phase 02 Layout Engine: 2G GSM TRX/ExtTrx two-column row (main.js
   make2gSector, .g2-trx-cols) is the app's one remaining component with
   zero responsive coverage of its own - it's authored via inline
   style.cssText, not the shared .field/.card/.grid system, so it needs an
   !important override here to win over that inline style. Confirmed via
   measurement: the fixed 1fr/1fr split first overflows the form column at
   ~410-430px viewport width (true phone widths); still fits cleanly at
   440px+ and above, so this only ever fires below that, never touching the
   already-correct tablet/desktop tiers. */
@media (max-width: 480px) {
  .g2-trx-cols { grid-template-columns: 1fr !important; }
}

/* ---- Phase 3: error report + invalid-row highlight ---- */
.ocip-error-box:empty { display: none; }
.ocip-error-list {
  background: var(--error-soft);
  border: 1px solid var(--error);
  border-radius: var(--r-lg);
  padding: var(--sp-3) var(--sp-4);
  color: var(--text);
}
.ocip-error-list-ttl { font-weight: 700; color: var(--error); margin-bottom: var(--sp-2); font-size: 0.8125rem; letter-spacing: .2px; }
.ocip-error-table { width: 100%; border-collapse: collapse; font-size: 0.78125rem; }
.ocip-error-table th, .ocip-error-table td {
  text-align: left; padding: 6px 10px;
  border-bottom: 1px solid rgba(225,91,82,.25);
  color: var(--text);
}
.ocip-error-table thead th {
  font-weight: 700; font-size: 0.6875rem; text-transform: uppercase; letter-spacing: .5px; color: var(--error);
}
.ocip-error-table tbody tr:last-child td { border-bottom: 0; }
.ocip-error-table td:first-child { font-variant-numeric: tabular-nums; width: 60px; }
.ocip-error-table td:nth-child(2) { width: 160px; color: var(--text-2); }
.ocip-table tbody tr.ocip-row-invalid td {
  background: rgba(225,91,82,.10);
  border-color: rgba(225,91,82,.25);
}
.ocip-table tbody tr.ocip-row-invalid td:first-child {
  box-shadow: inset 3px 0 0 var(--error);
}

/* ---- File Server (admin-only, __file_server__ page) ----
   Phase 01 UI shell only - no backend/data. Uses .layout.empty-workspace
   (already generic, see :736) for the single-column takeover; everything
   below is new, scoped under .fs-. */
.fs-shell {
  display: grid; grid-template-columns: 230px 1fr; gap: var(--sp-5);
  min-height: 420px;
}
/* Only the entries list scrolls (Doc 25 follow-up) - everything above it
   (search bar, breadcrumb, column headers) stays fixed in place. .form is
   normally the shared scroll container for every section (18+ of them), so
   this override is scoped to .fs-layout (set only by renderFileServerPage())
   instead of touching .form's base rule. Same flex-chain-to-a-bound pattern
   already used by .layout.home .form just above. */
.layout.fs-layout .form { overflow: hidden; display: flex; flex-direction: column; min-height: 0; }
.layout.fs-layout .fs-shell { flex: 1; min-height: 0; }
.fs-content { min-height: 0; }
.fs-sidebar {
  display: flex; flex-direction: column; gap: 2px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-3); align-self: start;
}
.fs-upload-btn { width: 100%; justify-content: center; margin-bottom: var(--sp-3); }
.fs-file-input { display: none; }
.fs-nav-item {
  position: relative;
  display: flex; align-items: center; gap: var(--sp-2); width: 100%; text-align: left;
  background: transparent; border: none; cursor: pointer;
  color: var(--text-2); font-family: var(--font); font-size: 0.84375rem; font-weight: 500;
  padding: 9px 10px 9px 12px; border-radius: var(--r-md);
  transition: background var(--t-fast), color var(--t-fast);
}
.fs-nav-item:hover { background: var(--panel2); color: var(--text); }
.fs-nav-item.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.fs-nav-item.active::before {
  content: ""; position: absolute; left: 2px; top: 6px; bottom: 6px;
  width: 3px; border-radius: var(--r-pill); background: var(--accent);
}
.fs-storage { margin-top: auto; padding: var(--sp-3) 10px 6px; border-top: 1px solid var(--border); }
.fs-storage-label { display: flex; justify-content: space-between; font-size: 0.75rem; color: var(--text-2); margin-bottom: 6px; }
.fs-storage-bar { height: 6px; border-radius: var(--r-pill); background: var(--panel2); overflow: hidden; }
.fs-storage-fill { height: 100%; border-radius: var(--r-pill); background: var(--accent); }
.fs-storage-sub { font-size: 0.71875rem; color: var(--muted); margin-top: 6px; }
.fs-content { display: flex; flex-direction: column; min-width: 0; }
.fs-toolbar { display: flex; align-items: center; gap: var(--sp-3); margin-top: 3px; margin-bottom: var(--sp-4); }
.fs-search { position: relative; flex: 1; max-width: 360px; display: flex; align-items: center; }
.fs-search .ic { position: absolute; left: 12px; width: 15px; height: 15px; color: var(--muted); pointer-events: none; }
.fs-search input {
  width: 100%; background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-md);
  color: var(--text-value); font-weight: 500; padding: 9px 12px 9px 36px; font-size: 0.84375rem; outline: none;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.fs-search input:focus { border-color: var(--accent); outline: 2px solid var(--accent); outline-offset: 1px; box-shadow: none; }
.fs-search input { padding-right: 30px; }
.fs-search-clear {
  display: inline-flex; align-items: center; justify-content: center;
  position: absolute; right: 6px; width: 22px; height: 22px;
  border: none; background: transparent; color: var(--muted);
  border-radius: var(--r-sm); cursor: pointer; transition: background var(--t-fast), color var(--t-fast);
}
.fs-search-clear:hover { color: var(--text); background: var(--panel2); }
.fs-search-clear .ic { position: static; left: auto; width: 13px; height: 13px; pointer-events: none; }
.fs-view-toggle {
  display: inline-flex; background: var(--glass); border: 1px solid var(--glass-border);
  border-radius: var(--r-md); padding: var(--sp-1); gap: var(--sp-1); flex: none;
}
.fs-view-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 26px; border: none; background: transparent; color: var(--muted);
  border-radius: 7px; cursor: pointer; transition: background var(--t-fast), color var(--t-fast);
}
.fs-view-btn .ic { width: 15px; height: 15px; }
.fs-view-btn:hover { color: var(--text); }
.fs-view-btn.active { background: var(--accent); color: var(--on-accent); box-shadow: var(--sh-2); }
/* All/Folders/Files filter - same pill-group look as .fs-view-toggle, text
   labels instead of icons since "folder vs file" needs to read unambiguous
   at a glance next to the view-mode icons. */
.fs-type-filter {
  display: inline-flex; background: var(--glass); border: 1px solid var(--glass-border);
  border-radius: var(--r-md); padding: var(--sp-1); gap: var(--sp-1); flex: none;
}
.fs-type-btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 26px; padding: 0 10px; border: none; background: transparent; color: var(--muted);
  border-radius: 7px; cursor: pointer; font-family: var(--font); font-size: 0.78125rem; font-weight: 500;
  white-space: nowrap; transition: background var(--t-fast), color var(--t-fast);
}
.fs-type-btn:hover { color: var(--text); }
.fs-type-btn.active { background: var(--accent); color: var(--on-accent); box-shadow: var(--sh-2); }
.fs-trash-actions { display: flex; gap: var(--sp-2); margin-bottom: var(--sp-4); }
/* .fs-main-outer owns the rounded border and clips via a plain (non-
   scrolling) overflow:hidden. .fs-main inside it is the actual scroller.
   Split into two elements on purpose: a position:sticky descendant
   (.fs-section-sticky) can paint outside a rounded overflow:auto ancestor
   while THAT ancestor is scrolling - a known Chromium quirk, seen live (a
   scrolled-past row briefly rendered above the sticky section header
   instead of staying clipped beneath it). A non-scrolling overflow:hidden
   ancestor one level further out doesn't have that bug, so it's a hard
   backstop clip regardless of what the inner scroller does. */
.fs-main-outer {
  flex: 1; min-height: 320px;
  border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden;
  display: flex; flex-direction: column;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.layout.fs-layout .fs-main-outer { min-height: 0; }
.fs-main-outer.drag-hover { border-color: var(--accent); background: var(--accent-soft); }
.fs-main {
  flex: 1; display: flex; align-items: center; justify-content: center;
  background: var(--panel); transition: background var(--t-fast);
}
.layout.fs-layout .fs-main { min-height: 0; }
.fs-empty { display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: var(--sp-6); gap: var(--sp-2); color: var(--muted); }
.fs-empty-ic {
  width: 48px; height: 48px; border-radius: var(--r-md);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel2); color: var(--text-2);
}
.fs-empty-ic .ic { width: 24px; height: 24px; }
.fs-empty-ttl { font-size: 0.875rem; font-weight: 600; color: var(--text-2); margin-top: var(--sp-1); }
.fs-empty-sub { font-size: 0.78125rem; color: var(--muted); max-width: 360px; line-height: 1.5; }
.fs-loading { color: var(--muted); font-size: 0.8125rem; }

/* Phase 02: real listing content overrides the empty-state centering. */
.fs-main:has(.fs-grid), .fs-main:has(.fs-list) {
  display: block; align-items: initial; justify-content: initial;
  /* No top padding here on purpose (moved onto .fs-section-sticky below) -
     any gap between the scroll clip's top edge and the sticky element
     itself is exactly where a scrolled-past row was found bleeding through
     (measured live: a folder row's cell content, not just empty space, at
     a y-coordinate a few px above the sticky header's own top). Zero gap
     means nothing can render there. */
  padding: 0 var(--sp-3) var(--sp-3); overflow-y: auto; overflow-x: hidden;
  transform: translateZ(0); /* belt-and-suspenders alongside the outer clip above */
}
.fs-breadcrumb { display: flex; align-items: center; gap: 4px; margin-bottom: var(--sp-3); flex-wrap: wrap; }
.fs-breadcrumb.hidden { display: none; }

/* Folders/Files section labels (Doc 10) - a section is only ever rendered
   when it has 1+ items, so no empty-header case to style. */
.fs-section-head { display: flex; align-items: baseline; gap: 6px; padding: 2px 6px 8px; }
/* Each section (Pinned Folders/Folders/Files) is its own sticky-header
   block: the section label + column header (when present) are wrapped
   together in ONE sticky element per section, instead of each sticking
   independently - two independently-sticky siblings at top:0 overlap
   rather than stack (a real bug found live: the section label ended up on
   top, silently swallowing clicks on the column sort buttons underneath
   it). Wrapping .fs-section-head/.fs-list-head inside a shared .fs-section
   (which bounds where the sticky block "unsticks") also keeps one
   section's header from lingering into the next section while scrolling. */
.fs-section-sticky { position: sticky; top: 0; z-index: 2; background: var(--panel); padding-top: var(--sp-3); }
.fs-section-ic { display: inline-flex; align-items: center; color: var(--accent); }
.fs-section-ic .ic { width: 13px; height: 13px; }
.fs-section-title { font-size: 0.78125rem; font-weight: 700; color: var(--text-2); }
.fs-section-count { font-size: 0.75rem; font-weight: 500; color: var(--muted); }
.fs-section + .fs-section {
  margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--border);
}
.fs-crumb {
  display: inline-flex; align-items: center; gap: 5px;
  background: transparent; border: none; cursor: pointer;
  color: var(--text-2); font-family: var(--font); font-size: 0.8125rem; font-weight: 500;
  padding: 3px 6px; border-radius: var(--r-sm);
}
.fs-crumb .ic { width: 14px; height: 14px; }
.fs-crumb:hover { color: var(--accent); background: var(--panel2); }
.fs-crumb:last-child { color: var(--text); font-weight: 600; }
.fs-crumb-sep { display: inline-flex; color: var(--muted); }
.fs-crumb-sep .ic { width: 13px; height: 13px; }

/* Selection: real checkboxes (appearance:none custom draw), NOT the app-wide
   iOS toggle skin that input[type=checkbox] gets everywhere else - list-item
   selection reads as a checkbox, not an on/off switch.
   IMPORTANT: the app-wide `input[type=checkbox]` rule (~line 500) has
   specificity (0,1,1) - element + attribute selector. A bare `.fs-select-check`
   class selector is only (0,1,0) and LOSES that cascade fight regardless of
   source order, silently rendering as the pill switch. Every rule here is
   qualified as `input.fs-select-check` (element + class = (0,1,1), tying the
   global rule and winning on source order since this block comes later) so
   it actually overrides it - verified by rendering, not just by reading the
   CSS (2026-08-06 regression: this had quietly never worked). */
.fs-select-bar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3) var(--sp-2); margin-bottom: var(--sp-3); min-height: 30px; }
.fs-select-bar.hidden { display: none; }
.fs-select-all { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font-size: 0.78125rem; color: var(--text-2); }
.fs-bulk-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.fs-bulk-actions.hidden { display: none; }
.fs-bulk-count { font-size: 0.78125rem; color: var(--text-2); font-weight: 600; margin-right: 2px; }
.fs-select-check-wrap { display: inline-flex; align-items: center; justify-content: center; flex: none; cursor: pointer; }
input.fs-select-check {
  appearance: none; -webkit-appearance: none; -moz-appearance: none; margin: 0; position: relative;
  width: 16px; height: 16px; border-radius: 4px; border: 1px solid var(--border);
  background: var(--panel2); cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast);
}
input.fs-select-check::before { content: none; } /* suppress the switch's sliding-dot pseudo-element */
input.fs-select-check:checked, input.fs-select-check:indeterminate { background: var(--accent); border-color: var(--accent); }
input.fs-select-check:checked::after {
  content: ""; position: absolute; left: 5px; top: 2px; width: 4px; height: 8px;
  border: solid var(--on-accent); border-width: 0 2px 2px 0; transform: rotate(45deg);
}
input.fs-select-check:indeterminate::after {
  content: ""; position: absolute; left: 3px; top: 6.5px; width: 8px; height: 2px; background: var(--on-accent);
}

/* "Select all" checkbox in the selection toolbar - same specificity-safe
   pattern, had zero override at all before (not even a losing one). */
input.fs-select-all-check {
  appearance: none; -webkit-appearance: none; -moz-appearance: none; margin: 0; position: relative;
  width: 16px; height: 16px; border-radius: 4px; border: 1px solid var(--border);
  background: var(--panel2); cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast);
}
input.fs-select-all-check::before { content: none; }
input.fs-select-all-check:checked, input.fs-select-all-check:indeterminate { background: var(--accent); border-color: var(--accent); }
input.fs-select-all-check:checked::after {
  content: ""; position: absolute; left: 5px; top: 2px; width: 4px; height: 8px;
  border: solid var(--on-accent); border-width: 0 2px 2px 0; transform: rotate(45deg);
}
input.fs-select-all-check:indeterminate::after {
  content: ""; position: absolute; left: 3px; top: 6.5px; width: 8px; height: 2px; background: var(--on-accent);
}

.fs-list { display: flex; flex-direction: column; gap: 2px; }
.fs-list .fs-entry { display: flex; align-items: center; gap: var(--sp-3); padding: 9px 10px; border-radius: var(--r-md); }
.fs-list .fs-entry-ic { width: 32px; height: 32px; }
.fs-list .fs-entry-info { flex: 1; min-width: 0; }
.fs-list .fs-entry-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fs-list .fs-entry-actions { margin-left: auto; }

/* Doc 13: List-view detail table (Type/Size/Uploaded/Modified). One shared
   grid-template-columns for the header row and every table-style entry row
   so cells line up. Trash rows (.fs-row-simple, no "created" field, not
   part of the sortable table) are untouched, still the plain flex row
   above. Higher specificity than ".fs-list .fs-entry" (display:flex) so
   this wins for ".fs-row-table" rows without needing !important. */
/* Column widths are CSS custom properties (JS-driven, see fsWireColumnResize/
   fsSetColWidth) with sensible fallbacks, so both this rule and every data
   row - which share it verbatim - resize together the instant fsMain's
   custom properties change, no per-row DOM writes needed. */
.fs-list-head, .fs-list .fs-entry.fs-row-table {
  display: grid;
  grid-template-columns: 32px 32px var(--fs-col-name, minmax(120px, 1fr))
    var(--fs-col-type, 90px) var(--fs-col-size, 90px)
    var(--fs-col-created, 120px) var(--fs-col-modified, 120px) 96px;
  align-items: center; gap: var(--sp-3);
}
.fs-list .fs-row-table .fs-entry-info { min-width: 0; }
.fs-list .fs-row-table .fs-entry-meta { display: none; }
.fs-list .fs-row-table .fs-entry-actions { margin-left: 0; }
.fs-col {
  font-size: 0.78125rem; color: var(--text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fs-col-uploaded, .fs-col-modified { color: var(--muted); }
.fs-grid .fs-col { display: none; }

.fs-list-head { padding: 4px 10px 8px; }
/* Windows Explorer-style column divider: a thin line at each resizable
   header cell's right edge, draggable to resize, double-click to auto-fit -
   see fsWireColumnResize()/fsAutoFitColumn() in main.js. */
.fs-col-head-cell { position: relative; display: flex; align-items: center; min-width: 0; height: 100%; }
.fs-col-resize-handle {
  position: absolute; top: 0; right: calc(var(--sp-3) / -2 - 1px); bottom: 0; width: 9px;
  cursor: col-resize; z-index: 1;
}
.fs-col-resize-handle::after {
  content: ""; position: absolute; top: 15%; bottom: 15%; left: 50%; width: 1px;
  background: var(--border); transition: background var(--t-fast), width var(--t-fast);
}
.fs-col-resize-handle:hover::after { background: var(--accent); width: 2px; }
body.fs-col-resizing { cursor: col-resize !important; user-select: none; }
body.fs-col-resizing .fs-col-resize-handle::after { background: var(--accent); width: 2px; }
.fs-sort-btn {
  display: inline-flex; align-items: center; gap: 4px; min-width: 0;
  background: transparent; border: none; cursor: pointer; padding: 2px 0;
  font-family: var(--font); font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .3px; color: var(--muted); text-align: left;
  user-select: none;
  transition: color var(--t-fast);
}
.fs-sort-btn:hover { color: var(--text-2); }
.fs-sort-btn.active { color: var(--accent); }
.fs-sort-arrow { font-size: 0.625rem; }

.fs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: var(--sp-3); }
.fs-grid .fs-entry {
  position: relative; display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 6px; padding: var(--sp-3) var(--sp-2); border-radius: var(--r-md);
}
.fs-grid .fs-select-check-wrap { position: absolute; top: 6px; left: 6px; }
.fs-grid .fs-entry-ic { width: 48px; height: 48px; }
.fs-grid .fs-entry-name { font-size: 0.8125rem; word-break: break-word; }
.fs-grid .fs-entry-meta { font-size: 0.6875rem; }

.fs-entry { background: transparent; border: 1px solid transparent; transition: background var(--t-fast), border-color var(--t-fast); }
.fs-entry:hover { background: var(--panel2); border-color: var(--border); }
/* Last-clicked row highlight - purely visual (focus/cursor cue), separate
   from actual selection which only ever happens via the checkbox. */
.fs-entry.active { background: var(--accent-soft); border-color: var(--accent); }
/* Internal drag-and-drop: drag any row onto a folder row to move it in. */
.fs-entry.dragging { opacity: 0.45; }
.fs-entry.drop-target { background: var(--accent-soft); border: 1px dashed var(--accent); }
.fs-entry-clickable { cursor: pointer; }
.fs-entry-ic {
  position: relative;
  flex: none; border-radius: var(--r-md); display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel2); color: var(--text-2);
}
.fs-entry-ic .ic { width: 18px; height: 18px; }
.fs-entry-ic-locked { color: var(--error); }
/* Image thumbnail - fills the same box the file-type glyph would (32px
   List / 48px Grid, see .fs-list/.fs-grid .fs-entry-ic above), so it's
   "bigger in Grid" automatically without a separate size rule here. */
.fs-entry-thumb { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
/* Pinned Folders badge (Doc 14/15) - small pin overlay on the folder icon. */
.fs-pin-badge {
  position: absolute; bottom: -3px; right: -3px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); color: var(--on-accent); border: 2px solid var(--panel);
}
.fs-pin-badge .ic { width: 8px; height: 8px; }
.fs-entry-name { font-size: 0.84375rem; font-weight: 500; color: var(--text); }
.fs-entry-meta { font-size: 0.71875rem; color: var(--muted); margin-top: 2px; }
.fs-entry-actions { display: flex; gap: 6px; flex: none; }
.fs-icon-btn {
  width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--border); border-radius: var(--r-sm);
  color: var(--text-2); cursor: pointer; transition: border-color var(--t-fast), color var(--t-fast);
}
.fs-icon-btn:hover { border-color: var(--accent); color: var(--accent); }
.fs-icon-btn .ic { width: 14px; height: 14px; }

/* Floating Drive-style upload-progress panel. */
.fs-upload-panel {
  position: fixed; bottom: 20px; right: 20px; width: 320px; max-height: 60vh;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--sh-3); z-index: var(--z-modal); overflow: hidden;
  display: flex; flex-direction: column;
}
.fs-upload-panel.hidden { display: none; }
.fs-upload-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 12px; border-bottom: 1px solid var(--border);
  font-size: 0.8125rem; font-weight: 600; color: var(--text);
}
.fs-upload-panel-head-btns { display: flex; align-items: center; gap: 4px; }
/* Doc 21: single primary action, context-aware (Cancel All while active,
   Clear Completed once everything's settled - never both, see
   updateUploadHeader()). */
.fs-upload-panel-primary-btn {
  background: transparent; border: none; cursor: pointer; color: var(--muted);
  font-family: var(--font); font-size: 0.71875rem; font-weight: 600; padding: 2px 6px; border-radius: var(--r-sm);
}
.fs-upload-panel-primary-btn:hover { color: var(--accent); }
.fs-upload-panel-more, .fs-upload-panel-close {
  background: transparent; border: none; cursor: pointer; color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center; padding: 2px; border-radius: var(--r-sm);
}
.fs-upload-panel-more:hover, .fs-upload-panel-close:hover { color: var(--text); }
.fs-upload-panel-more .ic, .fs-upload-panel-close .ic { width: 14px; height: 14px; }

/* Overall batch progress strip (explicit follow-up, 19 Aug 2026: file
   count, percentage, speed, ETA - all live, all summed across the whole
   batch, see updateUploadSummary()). Sits between the header and the
   per-file row list; stays visible while minimized (.fs-upload-panel-body
   below is what actually collapses), so the headline numbers are still
   readable with the panel shrunk down. */
.fs-upload-panel-summary { padding: 10px 12px; border-bottom: 1px solid var(--border); background: var(--panel2); }
.fs-upload-panel-summary.hidden { display: none; }
.fs-upload-summary-top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); margin-bottom: 6px; }
.fs-upload-summary-count { font-size: 0.75rem; font-weight: 600; color: var(--text); }
.fs-upload-summary-pct {
  font-size: 0.75rem; font-weight: 700; color: var(--accent);
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
}
.fs-upload-summary-bar { height: 6px; border-radius: var(--r-pill); background: var(--panel); overflow: hidden; }
.fs-upload-summary-fill {
  height: 100%; width: 0; border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--accent), var(--accent-2, var(--accent)));
  transition: width 150ms linear;
}
.fs-upload-summary-meta {
  margin-top: 6px; font-size: 0.6875rem; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* .fs-upload-panel-body's own height (not display:none) is what animates
   on Minimize/Restore, so it reads as a real collapse rather than an
   instant cut - the summary strip above stays put and live throughout. */
.fs-upload-panel-body {
  overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 4px;
  max-height: 320px; transition: max-height 200ms ease, padding 200ms ease;
}
.fs-upload-panel--minimized .fs-upload-panel-body { max-height: 0; padding-top: 0; padding-bottom: 0; overflow: hidden; }
.fs-upload-row { padding: 8px 8px 6px; border-radius: var(--r-md); }
.fs-upload-row-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: 6px; }
.fs-upload-row-name {
  font-size: 0.78125rem; color: var(--text-2); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; flex: 1; min-width: 0;
}
.fs-upload-row-pct { flex: none; font-size: 0.71875rem; font-weight: 600; color: var(--muted); }
.fs-upload-row-bar { height: 4px; border-radius: var(--r-pill); background: var(--panel2); overflow: hidden; }
.fs-upload-row-fill { height: 100%; width: 0; background: var(--accent); transition: width 120ms linear; }
.fs-upload-row-meta { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-top: 5px; }
.fs-upload-row-detail { font-size: 0.6875rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fs-upload-row-btns { flex: none; display: flex; gap: 2px; }
.fs-upload-row-btn {
  background: transparent; border: none; cursor: pointer; color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center; padding: 2px; border-radius: var(--r-sm);
}
.fs-upload-row-btn:hover { color: var(--text); background: var(--panel2); }
.fs-upload-row-btn .ic { width: 12px; height: 12px; }
.fs-upload-row.done .fs-upload-row-fill { background: var(--ok); }
.fs-upload-row.done .fs-upload-row-name { color: var(--text); }
.fs-upload-row.error .fs-upload-row-fill, .fs-upload-row.cancelled .fs-upload-row-fill { background: var(--error); }
.fs-upload-row.error .fs-upload-row-name, .fs-upload-row.cancelled .fs-upload-row-name { color: var(--error); }
.fs-upload-row.error .fs-upload-row-detail, .fs-upload-row.cancelled .fs-upload-row-detail { color: var(--error); }

/* Delete -> Undo toast (File Server only, see renderFileServerPage's
   showFsToast - the rest of the app deliberately has no toast pattern). */
.fs-toast-host {
  position: fixed; left: 20px; bottom: 20px; z-index: var(--z-modal);
  pointer-events: none;
}
.fs-toast {
  pointer-events: auto; display: flex; align-items: center; gap: var(--sp-3);
  background: var(--text); color: var(--bg); border-radius: var(--r-md);
  box-shadow: var(--sh-3); padding: 11px 14px; font-size: 0.8125rem;
  opacity: 0; transform: translateY(8px); transition: opacity var(--t-fast), transform var(--t-fast);
}
.fs-toast.show { opacity: 1; transform: translateY(0); }
.fs-toast-msg { white-space: nowrap; }
.fs-toast-action {
  flex: none; background: transparent; border: none; cursor: pointer;
  color: var(--accent-2, var(--accent)); font-family: var(--font); font-weight: 700;
  font-size: 0.8125rem; padding: 2px 4px;
}
.fs-toast-action:hover { text-decoration: underline; }

/* Windows-Explorer-style inline rename (New Folder default name). */
.fs-inline-edit-input {
  width: 100%; font: inherit; font-size: 0.84375rem; font-weight: 500; color: var(--text);
  background: var(--panel2); border: 1px solid var(--accent); border-radius: var(--r-sm);
  padding: 2px 6px; outline: none; box-shadow: var(--ring);
}
.fs-inline-edit-err {
  font-size: 0.6875rem; color: var(--error); margin-top: 3px;
}
.fs-inline-edit-err.hidden { display: none; }

/* Duplicate-file (Replace/Keep Both/Skip) dialog - three buttons. Smaller
   font/padding than the app's base .btn (explicit user request - the three
   long labels felt oversized), and the modal itself widens to fit all three
   on one line each (explicit follow-up: "keep it one line, make it bigger
   if that's what it takes" - "Replace Existing File" alone still wrapped
   to two/three lines at the narrower base 340px modal width). */
.modal-card:has(.fs-dup-actions) { width: 560px; }
.fs-dup-actions { flex-wrap: nowrap; justify-content: flex-end; }
.fs-dup-actions .btn { font-size: 0.75rem; padding: 8px 12px; white-space: nowrap; }
.fs-dup-filename {
  color: var(--text); font-weight: 600; font-size: 0.8125rem;
  background: var(--panel2); border-radius: var(--r-sm); padding: 6px 10px; margin-top: 6px;
  word-break: break-word;
}
.fs-dup-apply-all {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-top: var(--sp-4); color: var(--text-2); font-size: 0.8125rem; cursor: pointer;
}

.fs-share-row { display: flex; gap: var(--sp-2); align-items: center; }
.fs-share-row input { flex: 1; min-width: 0; font-size: 0.78125rem; }
.fs-share-row .btn-mini { flex: none; display: inline-flex; align-items: center; justify-content: center; padding: 0; width: 32px; height: 32px; }
.fs-share-row .btn-mini .ic { width: 14px; height: 14px; }
.fs-share-row .btn-mini.copied { border-color: var(--ok); color: var(--ok); }

/* Folder Lock (Doc 25) password fields - same input treatment as
   .modal-card input, with a show/hide (eye) toggle inside the field like
   .fs-search-clear, plus a "Forgot password?" link on the Unlock dialog. */
.fs-pw-field { position: relative; margin: var(--sp-2) 0; }
.fs-pw-field input { width: 100%; padding-right: 38px; }
.fs-pw-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  width: 26px; height: 26px; border: none; background: transparent; color: var(--muted);
  border-radius: var(--r-sm); cursor: pointer; transition: background var(--t-fast), color var(--t-fast);
}
.fs-pw-toggle:hover { color: var(--text); background: var(--panel2); }
.fs-pw-toggle .ic { width: 15px; height: 15px; pointer-events: none; }

/* Archive Browser modal (Doc 17) - browse a ZIP/RAR/7Z/TAR without
   extracting it. Wider than the base .modal-card; everything else (header,
   breadcrumb, rows) reuses existing File Server classes for a consistent
   look, just re-scoped to fit inside a modal. */
.fs-archive-card { width: 720px; max-width: 92vw; }
.fs-archive-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-3); }
.fs-archive-header h3 { margin: 0; word-break: break-word; }
.fs-archive-crumb { margin-bottom: var(--sp-3); }
.fs-archive-error {
  color: var(--error); background: rgba(225,91,82,.1); border: 1px solid rgba(225,91,82,.25);
  border-radius: var(--r-md); padding: var(--sp-3); font-size: 0.8125rem; margin-bottom: var(--sp-3);
}
.fs-archive-panes { display: flex; gap: var(--sp-3); height: 380px; }
.fs-archive-list-pane {
  width: 260px; flex: none; overflow-y: auto; padding-right: var(--sp-3);
  border-right: 1px solid var(--border); display: flex; flex-direction: column; gap: 2px;
}
.fs-archive-row { padding: 8px 8px; }
.fs-archive-empty, .fs-archive-preview-none { color: var(--muted); font-size: 0.8125rem; text-align: center; padding: var(--sp-4); }
.fs-archive-preview-none { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); }
.fs-archive-preview {
  flex: 1; min-width: 0; overflow: auto; border-radius: var(--r-md); background: var(--panel2);
  display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 0.8125rem;
  padding: var(--sp-3);
}
.fs-archive-preview-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--r-sm); }
.fs-archive-preview-pdf { width: 100%; height: 100%; border: none; border-radius: var(--r-sm); background: var(--panel); }
.fs-archive-preview-text {
  width: 100%; height: 100%; margin: 0; overflow: auto; white-space: pre-wrap; word-break: break-word;
  color: var(--text); font-family: ui-monospace, "Cascadia Code", "Consolas", monospace; font-size: 0.75rem; text-align: left;
  align-self: flex-start;
}

/* File Preview & Online Text Editor - reuses .fs-archive-card/-header/
   -preview (above) as-is for a consistent look with the Archive Browser;
   .fs-preview-body is just a fixed-height flex wrapper so the reused
   .fs-archive-preview's `flex: 1` sizes correctly outside its usual
   .fs-archive-panes parent. */
.fs-preview-body { display: flex; height: 480px; }
.fs-preview-actions { display: flex; align-items: center; gap: var(--sp-2); }

/* Maximize toggle (image + PDF + xlsx + text viewers) - overrides
   .fs-archive-card's/.fs-preview-body's fixed sizing above; wins on source
   order, same specificity, no !important needed. */
.fs-preview-card--maximized { width: 96vw; max-width: 96vw; }
.fs-preview-card--maximized .fs-preview-body { height: 88vh; }

/* Same maximize toggle, applied to the other two "big content" windows
   (explicit follow-up, 20 Aug 2026) - Archive Browser and the folder
   picker tree, each with their own fixed-height content area to override. */
.fs-archive-card--maximized { width: 96vw; max-width: 96vw; }
.fs-archive-card--maximized .fs-archive-panes { height: 88vh; }
.fs-picker-card--maximized { width: 96vw; max-width: 96vw; }
.fs-picker-card--maximized .fs-tree { max-height: 76vh; }

/* Professional image viewer (Doc: maximize + zoom + Prev/Next, 18 Aug
   2026) - a dedicated container instead of reusing .fs-archive-preview,
   since panning needs overflow:hidden + a transformed <img> rather than
   native scroll. */
.fs-image-viewer {
  position: relative; flex: 1; min-width: 0; overflow: hidden;
  border-radius: var(--r-md); background: var(--panel2);
  display: flex; align-items: center; justify-content: center;
}
.fs-image-viewer--loading { opacity: .5; }
.fs-image-viewer-img {
  max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--r-sm);
  transform-origin: center center; cursor: zoom-in;
  transition: transform var(--t-fast);
}
.fs-image-viewer--zoomed .fs-image-viewer-img { cursor: grab; transition: none; }
.fs-image-viewer--dragging .fs-image-viewer-img { cursor: grabbing; }

/* Canvas-rendered, continuous-scroll PDF viewer (explicit follow-up, 19
   Aug 2026: pdf.js draws each page onto its own <canvas>, appended here in
   order - a plain vertical stack, centered, with breathing room between
   pages and a subtle card shadow per page so each one reads as a distinct
   sheet, Drive/Acrobat-style. Zoom re-renders the canvases themselves (see
   renderAllPdfPages in main.js) rather than a CSS transform, so pages stay
   crisp at any zoom level. Default cursor/scroll is native (wheel,
   scrollbar, drag-select); the Hand tool toggle (--hand/--dragging below)
   switches to Acrobat/Drive-style click-drag panning instead - only
   possible now that pages are real same-document <canvas> elements, not
   the old cross-document <iframe>. */
.fs-pdf-viewer {
  flex: 1; min-width: 0; overflow: auto; border-radius: var(--r-md); background: var(--panel2);
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-4); padding: var(--sp-4);
}
.fs-pdf-viewer--loading { opacity: .5; }
/* Custom black-outlined, white-filled hand cursor (explicit follow-up, 19
   Aug 2026: first pass used only a stroked outline - "fill the inside
   white too, not just black outline lines" - then, separately, the native
   `cursor: grab` glyph itself rendered too light/white to see against the
   white page canvases underneath). Same "hand" icon as the toolbar button,
   every path (palm/wrist outline plus the three finger-crease lines)
   filled off-white with a black outline so the whole glyph reads as one
   solid shape rather than hollow line art - Lucide's "hand" is stroke-
   only by design, so each finger-crease path is an open curve; filling it
   too just adds a thin filled sliver along that curve (still reads
   correctly at cursor size), which is what "not empty/hollow" called for. */
.fs-pdf-viewer--hand {
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='17' height='17' viewBox='0 0 24 24' stroke='%23000000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15' fill='%23f0f0f0'/><path d='M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2' fill='%23f0f0f0'/><path d='M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2' fill='%23f0f0f0'/><path d='M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8' fill='%23f0f0f0'/></svg>") 7 6, grab;
}
/* Distinct closed-fist glyph while actively dragging (explicit follow-up,
   19 Aug 2026: "the hand needs to move/change to show it's gripping") -
   swapping the cursor image itself, not just its CSS keyword, since the
   visible shape is what actually communicates "now grabbing" versus the
   open-palm hand above. */
.fs-pdf-viewer--hand.fs-pdf-viewer--dragging {
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='17' height='17' viewBox='0 0 24 24' stroke='%23000000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-4a8 8 0 0 1-8-8 2 2 0 1 1 4 0' fill='%23f0f0f0'/><path d='M18 11.5V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1.4' fill='%23f0f0f0'/><path d='M14 10V8a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2' fill='%23f0f0f0'/><path d='M10 9.9V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v5' fill='%23f0f0f0'/><path d='M6 14a2 2 0 0 0-2-2a2 2 0 0 0-2 2' fill='%23f0f0f0'/></svg>") 7 6, grabbing;
  user-select: none;
}
.fs-pdf-page { background: #fff; box-shadow: var(--sh-2); border-radius: 2px; flex: none; }
.fs-preview-zoom-label {
  color: var(--text-2); font-size: 0.75rem; font-weight: 600; min-width: 40px; text-align: center;
  user-select: none;
}
/* Prev/Next: a translucent scrim over the image itself (not the app's
   panel/border tokens) - the only legible, theme-independent choice over
   arbitrary photo content, same pattern every image lightbox uses. */
.fs-image-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 1;
  width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.45); border: 1px solid rgba(255,255,255,.18); border-radius: var(--r-pill);
  color: #fff; cursor: pointer; transition: background var(--t-fast);
}
.fs-image-nav:hover { background: rgba(0,0,0,.65); }
.fs-image-nav .ic { width: 20px; height: 20px; }
.fs-image-nav-prev { left: var(--sp-3); }
.fs-image-nav-next { right: var(--sp-3); }

/* xlsx grid viewer/editor (ExcelJS, static/vendor/exceljs/) - a plain
   scrollable <table>, not a canvas/textarea like the other kinds. Own
   background/border tokens (not --preview-bg/--mono like the plain-text
   editor) since a data grid reads better in the regular UI font than
   monospace, and each cell's own fill/font (set inline in main.js,
   straight from the source file's own styling) needs a neutral surface
   underneath it to show against. */
.fs-xlsx-viewer {
  flex: 1; min-width: 0; overflow: hidden; border-radius: var(--r-md);
  background: var(--panel2); display: flex; flex-direction: column;
}
.fs-xlsx-viewer--loading { opacity: .5; }
/* Table scrolls in its own sub-area so the tab bar below stays pinned,
   Excel-style, instead of scrolling away with a tall/wide sheet. */
.fs-xlsx-table-wrap { flex: 1; min-height: 0; overflow: auto; }
.fs-xlsx-table { border-collapse: collapse; font-size: 0.8125rem; color: var(--text); }
.fs-xlsx-cell {
  border: 1px solid var(--border); padding: 4px 8px; min-width: 60px;
  white-space: nowrap; outline: none;
}
.fs-xlsx-cell[contenteditable="true"]:focus {
  outline: 2px solid var(--accent); outline-offset: -2px; background: var(--accent-soft);
}
/* Formula cells are always read-only (no in-browser recalculation engine) -
   the "ƒ" badge plus italic makes that visually obvious inline, not
   just via the title tooltip main.js sets. */
.fs-xlsx-cell--formula { color: var(--text-2); font-style: italic; cursor: default; }
.fs-xlsx-cell--formula::before { content: "\0192 "; color: var(--accent); font-weight: 700; font-style: normal; }

/* Excel-style sheet tabs along the bottom of the grid (explicit follow-up,
   20 Aug 2026: "switch between sheets, add a sheet, like real Excel"). */
.fs-xlsx-tabbar {
  flex: none; display: flex; align-items: center; gap: 2px; overflow-x: auto;
  padding: var(--sp-2); border-top: 1px solid var(--border);
}
.fs-xlsx-tab {
  flex: none; padding: 6px 12px; border-radius: var(--r-sm); border: 1px solid transparent;
  background: none; color: var(--text-2); font-size: 0.75rem; cursor: pointer;
  white-space: nowrap; transition: background var(--t-fast), color var(--t-fast);
}
.fs-xlsx-tab:hover { background: var(--panel); color: var(--text); }
.fs-xlsx-tab.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.fs-xlsx-tab-add {
  flex: none; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm); border: 1px dashed var(--border); background: none;
  color: var(--text-2); cursor: pointer; transition: border-color var(--t-fast), color var(--t-fast);
}
.fs-xlsx-tab-add:hover { border-color: var(--accent); color: var(--accent); }
.fs-xlsx-tab-add .ic { width: 14px; height: 14px; }

/* Folder color/icon picker (Manager-only "Icon" row-menu item, 18 Aug 2026). */
.fs-appearance-card { width: 380px; max-height: 90vh; overflow-y: auto; }
.fs-appearance-preview { display: flex; justify-content: center; margin: var(--sp-2) 0 var(--sp-4); }
.fs-appearance-preview-ic {
  width: 44px; height: 44px; border-radius: var(--r-md); background: var(--panel2);
  display: flex; align-items: center; justify-content: center;
}
.fs-appearance-preview-ic .ic { width: 24px; height: 24px; }
.fs-appearance-label {
  font-size: 0.6875rem; font-weight: 700; letter-spacing: .3px; text-transform: uppercase;
  color: var(--text-label); margin: var(--sp-3) 0 var(--sp-2);
}
.fs-appearance-swatches, .fs-appearance-icons { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
/* Each swatch is a folder glyph rendered in that color (not a plain color
   circle) - "many folders side by side, each a different color", same
   look as Windows' own folder-color picker. */
.fs-appearance-swatch {
  width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel2); border: 2px solid transparent; border-radius: var(--r-sm); cursor: pointer;
  padding: 0; transition: border-color var(--t-fast), transform var(--t-fast);
}
.fs-appearance-swatch .ic { width: 20px; height: 20px; }
.fs-appearance-swatch:hover { transform: scale(1.1); }
.fs-appearance-swatch.active { border-color: var(--text); }
.fs-appearance-icon-btn {
  width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-sm);
  color: var(--text-2); cursor: pointer; transition: border-color var(--t-fast), color var(--t-fast);
}
.fs-appearance-icon-btn:hover { border-color: var(--accent); color: var(--accent); }
.fs-appearance-icon-btn.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.fs-appearance-icon-btn .ic { width: 16px; height: 16px; }
.fs-preview-textarea {
  width: 100%; height: 100%; margin: 0; resize: none; border: none; outline: none;
  border-radius: var(--r-sm); padding: var(--sp-2);
  /* Same visual language as the generated-code Preview panel
     (--preview-bg/--preview-fg/--mono) - "this is a file's raw content",
     not a form field. */
  background: var(--preview-bg); color: var(--preview-fg);
  font-family: var(--mono); font-size: 0.75rem; line-height: 1.55;
}

.fs-empty-action { margin-top: var(--sp-2); }

/* Right-click / "..." kebab context menu (Move, Copy, Delete, ...). */
.fs-ctx-menu {
  position: fixed; z-index: var(--z-modal); min-width: 168px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: var(--sh-3); padding: 4px; display: flex; flex-direction: column;
}
.fs-ctx-item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  background: transparent; border: none; cursor: pointer; border-radius: 6px;
  color: var(--text); font-family: var(--font); font-size: 0.8125rem; padding: 8px 10px;
  user-select: none;
}
.fs-ctx-item:hover { background: var(--panel2); }
.fs-ctx-item .ic { width: 14px; height: 14px; flex: none; color: var(--text-2); }
.fs-ctx-item.danger { color: var(--error); }
.fs-ctx-item.danger .ic { color: var(--error); }
.fs-ctx-item.disabled { cursor: not-allowed; opacity: 0.4; }
.fs-ctx-item.disabled:hover { background: transparent; }
.fs-ctx-sep { height: 1px; background: var(--border); margin: 4px 4px; }

/* Toolbar "Sort" button + its Google Drive-style panel (Sort by / direction
   / Folders on-top-vs-mixed) - reuses .fs-ctx-menu's box/positioning, just
   wider (fits "Mixed with files") and with checkmarked radio-style rows
   instead of plain action items. */
.fs-sort-toolbar-btn { display: inline-flex; align-items: center; gap: 6px; }
.fs-sort-menu { min-width: 200px; }
.fs-sort-menu-heading {
  font-size: 0.6875rem; font-weight: 800; letter-spacing: .4px; text-transform: uppercase;
  color: var(--muted); padding: 8px 10px 4px;
}
.fs-sort-menu-item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  background: transparent; border: none; cursor: pointer; border-radius: 6px;
  color: var(--text); font-family: var(--font); font-size: 0.8125rem; padding: 8px 10px;
  user-select: none;
}
.fs-sort-menu-item:hover { background: var(--panel2); }
.fs-sort-menu-item.active { color: var(--accent); font-weight: 600; }
.fs-sort-menu-check { width: 14px; height: 14px; flex: none; color: var(--accent); }
.fs-sort-menu-check .ic { width: 14px; height: 14px; }

/* Move/Copy destination folder-picker modal (Doc, 19 Aug 2026) - a
   collapsible tree instead of the old breadcrumb-per-level browser
   (explicit, repeated follow-up, applies to every Move/Copy in the app -
   see openFolderPicker). */
.fs-picker-card { width: 420px; }
.fs-tree {
  max-height: 320px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 4px; margin-top: var(--sp-2);
}
.fs-tree-row {
  display: flex; align-items: center; gap: 6px; width: 100%; text-align: left;
  cursor: pointer; border-radius: var(--r-sm);
  color: var(--text); font-family: var(--font); font-size: 0.8125rem; padding: 7px 9px;
}
.fs-tree-row:hover { background: var(--panel2); }
.fs-tree-row.sel { background: var(--accent-soft); color: var(--accent); }
.fs-tree-arrow {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 18px; height: 18px; background: none; border: none; cursor: pointer;
  color: var(--text-2); padding: 0; border-radius: var(--r-sm);
  transition: transform var(--t-fast);
}
.fs-tree-arrow:hover { background: var(--panel); color: var(--text); }
.fs-tree-arrow.open { transform: rotate(90deg); }
.fs-tree-arrow .ic { width: 14px; height: 14px; }
.fs-tree-ic { display: inline-flex; flex: none; color: var(--text-2); }
.fs-tree-ic .ic { width: 15px; height: 15px; }
.fs-tree-row.sel .fs-tree-ic { color: var(--accent); }
.fs-tree-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fs-tree-children.hidden { display: none; }
.fs-picker-empty { padding: var(--sp-2) var(--sp-2) var(--sp-2) 36px; color: var(--muted); font-size: 0.75rem; }

/* Share Folder (Doc, 19 Aug 2026) - the Generate Link bar, prepended as the
   first child of .fs-main whenever My Files is standing at the Share
   Folder root (see renderEntries()). It's a normal .fs-list listing
   underneath (same as any other folder - explicit, repeated follow-up),
   so this only needs to pin itself above that scrolling content, same
   idiom as .fs-section-sticky above (position:sticky, solid background so
   scrolled-past rows can't bleed through underneath it). The block/
   scrollable override below matches :has(.fs-list) exactly (not just a
   different padding) so an EMPTY Share Folder (bar + .fs-empty, no
   .fs-list yet) still lays out the same way instead of falling back to
   .fs-main's default flex-centered single-item mode. */
.fs-main:has(.fs-share-bar) {
  display: block; align-items: initial; justify-content: initial;
  padding: 0 var(--sp-3) var(--sp-3); overflow-y: auto; overflow-x: hidden;
}
.fs-share-bar {
  position: sticky; top: 0; z-index: 3; margin-bottom: var(--sp-3);
  display: flex; align-items: center; gap: var(--sp-3); width: 100%;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  /* Same fix as .modal-card/.btn elsewhere (reported live, same symptom):
     titles/labels aren't selectable text, so hovering them shouldn't flash
     a text (I-beam) cursor. Text/number inputs opt back in below. */
  user-select: none;
}
.fs-share-bar input[type=text], .fs-share-bar input[type=number] { user-select: text; }
.fs-share-bar input[type=number] { width: 72px; flex: none; background: var(--panel); color-scheme: dark; }
.fs-share-unit-wrap { position: relative; flex: none; display: inline-flex; }
.fs-share-unit-wrap select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  width: auto; padding-right: 30px; background: var(--panel);
}
.fs-share-unit-arrow {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%) rotate(90deg);
  pointer-events: none; color: var(--text-2); display: flex;
}
.fs-share-unit-arrow .ic { width: 12px; height: 12px; }
.fs-share-bar .fs-share-gen-btn { flex: none; }
.fs-share-bar input[type=text] {
  flex: none; width: 497px; max-width: 54%; background: var(--panel);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fs-share-bar .btn-mini { flex: none; }
.fs-share-bar-err { color: var(--error); font-size: 0.78125rem; flex: none; }

/* ---- DOT Deletion (module: rule1) ----
   Standard two-column page (not empty-workspace): .r1-page sits in the left
   form column, the shared #preview panel stays on the right untouched.
   Layout finalised 2026-07-25 (DOT_Deletion_Enterprise_Complete_Specification.md).
   27 Jul 2026: the uploaded-file card + status is .r1-page's first child
   (inside #form), exactly like Active Configuration's "Sectors" card is
   the first child of its own pane - so both sit in the same .layout row
   Preview aligns against, giving Preview the exact same top edge as the
   upload card with zero CSS positioning tricks (KAREEM_TEQ_DOT_Deletion_
   Layout_Refinement.md). This was tried the other way too (upload card
   living in #page-toolbar, non-scrolling) to satisfy an earlier "must stay
   fixed on scroll" requirement - that requirement was explicitly cancelled
   in favour of this alignment rule (see PROJECT_HANDOVER.md 8.16.5+ for
   the back-and-forth on this exact spot). Business logic/backend
   untouched throughout. */
.r1-file-input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.r1-page { display: flex; flex-direction: column; }

/* File card + status: no margin-top of its own - .card (Sectors) has none
   either, both rely solely on #page-toolbar's own margin-bottom
   (.ip-toolbar-slot) for the tabs -> content gap, so neither tab
   double-counts it (see .ia-seg-wrap's own comment). */
.r1-import-wrap {
  display: flex; flex-direction: column; gap: var(--sp-3);
}
.r1-work-area { margin-top: var(--sp-3); }

/* Working area: DOT chips + AntennaUnitGroup, inside #form (scrolls). */
.r1-chips-card { margin-top: 0; }

/* Empty state - entire card is clickable + a drag/drop target. */
.r1-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-2);
  width: 100%; min-height: 130px;
  background: var(--panel);
  border: 1px dashed var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-5) var(--sp-4);
  cursor: pointer; color: var(--text);
  transition: border-color var(--t-fast), background var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
  text-align: center;
}
.r1-drop:hover {
  border-color: var(--accent); background: var(--surface-4, var(--panel2));
  transform: translateY(-1px); box-shadow: var(--sh-md);
}
.r1-drop:focus-visible { outline: none; box-shadow: var(--ring, 0 0 0 3px rgba(22,163,148,.18)); }
.r1-drop.drag-hover {
  border-color: var(--accent); border-style: dashed;
  background: var(--accent-soft);
}
.r1-drop-ic {
  width: 44px; height: 44px; border-radius: var(--r-lg);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent);
}
.r1-drop-ic .ic { width: 22px; height: 22px; }
.r1-drop-title { font-size: 0.875rem; font-weight: 600; color: var(--text); }
.r1-drop-hint { font-size: 0.75rem; color: var(--muted); }

/* Ready state - compact file summary row, ~35-40% shorter than the upload
   card (tight padding, no wasted vertical space). */
.r1-summary {
  display: flex; align-items: center; gap: var(--sp-3);
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-2) var(--sp-4);
}
.r1-sum-ic {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: var(--r-md);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent);
}
.r1-sum-ic .ic { width: 16px; height: 16px; }
.r1-sum-meta { flex: 1 1 auto; min-width: 0; }
.r1-sum-top { display: flex; align-items: center; gap: var(--sp-3); }
.r1-sum-name {
  font-size: 0.8125rem; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.r1-sum-ready {
  flex-shrink: 0; display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: .4px;
  color: var(--ok, var(--accent));
}
.r1-sum-ready .ic { width: 12px; height: 12px; }
.r1-sum-sub { display: block; font-size: 0.71875rem; color: var(--muted); margin-top: 1px; }
.r1-sum-actions { flex-shrink: 0; display: flex; gap: var(--sp-2); }

/* Persistent Status Card - single source of status/error text for this page
   (spec: ~40px height, small padding, no layout shift, visible until the
   next status). */
.r1-status {
  display: flex; align-items: center; gap: var(--sp-2);
  align-self: flex-start; width: fit-content; max-width: 100%;
  background: var(--panel2); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 9px var(--sp-4);
  min-height: var(--control-h); box-sizing: border-box;
  font-size: 0.78125rem; font-weight: 500; color: var(--text-2);
  transition: border-color var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.r1-status-ic { display: inline-flex; flex-shrink: 0; }
.r1-status-ic .ic { width: 16px; height: 16px; }
.r1-status.busy { color: var(--accent); border-color: var(--accent); }
.r1-status.busy .r1-status-ic .ic { animation: ocip-spin 1s linear infinite; }
.r1-status.ok { color: var(--ok, var(--accent)); border-color: var(--ok, var(--accent)); background: var(--accent-soft); }
.r1-status.err { color: var(--error); border-color: var(--error); background: var(--error-soft); }

/* DOT chips card */
.r1-chips-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-5);
}
.r1-chips-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-3); margin-bottom: var(--sp-3);
}
.r1-chips-title { font-size: 0.8125rem; font-weight: 700; color: var(--text); }
.r1-chips-count { font-size: 0.75rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.r1-chips-actions { display: flex; gap: var(--sp-2); margin-bottom: var(--sp-4); }

/* DOT grid (AntennaUnitGroup_UI_Functional_Improvements_Prompt.md, 27 Jul
   2026 - supersedes the auto-fill/row-major version from the same-day
   layout redesign, which was flagged to the user as a deviation from the
   original 2026-07-25 "final" column-major rule; this spec is the direct
   answer, restoring a FIXED column count + column-major fill, just at 4
   columns instead of 8 to match the panel's new narrower width): always
   EXACTLY 4 columns, filled column-first (col 1 = DOT1-8, col 2 = DOT9-16,
   ...). Extra DOTs grow row count only (rows = max(8, ceil(count/4))),
   computed in JS (--r1-rows) - same mechanism as the original 8-column
   version, just re-based on 4. Chip text still doesn't truncate/ellipsize
   (unchanged from the redesign pass) - wraps to a 2nd line inside the chip
   instead. A max-height safety cap still applies for pathological counts so
   the page/Preview never have to move. */
.r1-chips-scroll {
  max-height: 340px;
  overflow-y: auto; overflow-x: hidden;
  padding-right: var(--sp-1);
}
.r1-chips-grid {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(var(--r1-rows, 8), auto);
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-2);
}
.r1-chip {
  font-family: 'JetBrains Mono', monospace; font-size: 0.75rem; font-weight: 600;
  padding: 6px 4px; border-radius: var(--r-md);
  background: var(--panel2); border: 1px solid var(--border); color: var(--text-2);
  cursor: pointer; text-align: center;
  white-space: normal; word-break: break-word; line-height: 1.3;
  transition: border-color var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.r1-chip:hover { border-color: var(--accent); color: var(--text); }
.r1-chip.is-selected {
  background: var(--accent-dk); border-color: var(--accent); color: var(--accent-hv);
}
.r1-chips-empty { font-size: 0.8125rem; color: var(--muted); padding: var(--sp-3) 0; }

/* ---- DOT Deletion: AntennaUnitGroup panel (DOT_Deletion_Engine_UI_UX_Design.md
   27 Jul 2026, rebalanced by DOT_Deletion_Generator_UI_Layout_Redesign.md same
   day). UI/UX only - no backend/generation/mapping logic. Right column inside
   the DOT Deletion pane itself (NOT the shared #preview panel, which stays
   untouched on the far right like every other page). .r1-work-area only
   ever has two children (chipsCard, augCol) and they naturally line up in a
   single grid row - no explicit grid-row placement hack needed.
   .r1-aug-col reuses .r1-chips-card's exact panel treatment (background/
   border/radius/padding) for visual parity between the two panels at their
   2:1 widths. */
.r1-page.has-aug .r1-work-area {
  display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  column-gap: var(--sp-5);
  align-items: start;
}
.r1-aug-col {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-5);
  display: flex; flex-direction: column;
}
.r1-aug-title { font-size: 1rem; font-weight: 600; color: var(--text); }
/* Compact "Groups [v]" selector, directly under the title (spacing tightened
   6px per AntennaUnitGroup_UI_Functional_Improvements_Prompt.md, was 8px). */
/* flex: none overrides the generic .field's "flex: 1 1 140px" - without it,
   this field (a direct child of .r1-aug-col, a column flexbox) grows to
   fill all leftover vertical space instead of sizing to its own content,
   which is exactly what produced the large empty gap above "Group 1". */
.r1-aug-groups-field { margin-top: 6px; max-width: 140px; flex: none; }
.r1-aug-cards { display: flex; flex-direction: column; margin-top: var(--sp-4); }
.r1-aug-group { padding: 12px 0; }
/* Light divider between groups (16px each side) instead of a heavy nested card. */
.r1-aug-group:not(:first-child) { border-top: 1px solid var(--border); margin-top: var(--sp-4); }
.r1-aug-group:first-child { padding-top: 0; }
.r1-aug-group:last-child { padding-bottom: 0; }
.r1-aug-group-title { font-size: 0.8125rem; font-weight: 600; color: var(--text-heading); margin-bottom: var(--sp-2); }
/* max-width:none (not .wide's min-width:280px, which overflowed this
   narrow column) so the Name field uses the panel's actual available
   width instead of capping at the generic .field's 200px. */
.r1-aug-group .field { gap: var(--sp-1); max-width: none; }
.r1-aug-group .field label { font-size: 0.75rem; }
.r1-aug-tx-label {
  color: var(--accent); font-weight: 700; font-size: 0.75rem;
  margin-top: var(--sp-3); margin-bottom: 6px;
}
/* Transceivers: single row, buttons shrunk again per this spec ("reduce
   size compared to current implementation" - was 28-32px). */
/* 2026-07-28: single bordered frame around the group (user asked to drop
   the divider-grid look but keep one shared frame) - same formula as the
   .segmented tab-switcher container (glass background, rounded frame,
   padding+gap between pills), not a rigid divided grid. */
.r1-aug-tx-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); max-width: 140px;
  gap: var(--sp-1); background: var(--glass);
  border: 1px solid var(--glass-border); border-radius: var(--r-md);
  padding: var(--sp-1);
}
/* Live "exactly 2" validation warning at Generate-click time - reuses the
   app's one .field-err-msg style (no new error-text pattern) + a light red
   frame around the grid, the same technique 5G Active's own Active
   Configuration transceiver rule already uses (.tx-field.field--error
   .port-row). */
.r1-aug-tx-grid.has-error { border-color: var(--error-2); }
.r1-aug-tx-btn {
  padding: 6px 8px;
  background: transparent; border: none; border-radius: 7px; color: var(--text-2);
  font-family: var(--font); font-weight: 700; font-size: 0.6875rem;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.r1-aug-tx-btn:hover { color: var(--text); background: var(--surface-4); }
/* Selected: solid fill per spec ("Primary color, white text") - uses
   --on-accent (the app's existing text-on-accent-fill token, same one
   .btn.accent/.seg-btn.active use) rather than literal white, since pure
   white text is forbidden by DESIGN_SYSTEM.md's Never-Change color rules. */
.r1-aug-tx-btn.is-selected { background: var(--accent); color: var(--on-accent); }
/* Disabled: JS now sets the native `disabled` attribute once a group has 2
   selected (AntennaUnitGroup_UI_Functional_Improvements_Prompt.md "exactly
   two" rule) - this state is live/functional now, not just reserved
   styling. */
.r1-aug-tx-btn:disabled, .r1-aug-tx-btn.is-disabled {
  background: transparent; color: var(--muted);
  opacity: .5; cursor: not-allowed;
}
@media (max-width: 1024px) {
  .r1-page.has-aug .r1-work-area { grid-template-columns: 1fr; }
}

/* ---- 4G Transport: General card ----
   Ethernet Port Name and 4G RAN VLAN get a fixed, modest column width
   (narrower than IP/Gateway need, but not squeezed to bare dropdown
   content); 4G RAN IP/Gateway share the rest evenly instead of stretching
   to fill all the leftover width. Column 1 is a fixed px (not
   content/max-content) on purpose: Custom Port Name's own intrinsic width
   is wider than the dropdown's, so a content-based track would grow when
   Custom Port Name appears and shift IP/Gateway/VLAN out of position -
   fixed keeps column 1 identical in both states. Custom Port Name stacks
   under the dropdown only, within column 1, same width, never wider. It is
   fully removed from layout (display:none, main.js) rather than just
   hidden while a predefined port is selected, so no height is reserved for
   it and the card wraps tightly around whichever state is showing. */
.t4g-general-grid {
  display: grid;
  grid-template-columns: 160px minmax(150px, 1fr) minmax(150px, 1fr) 140px;
  align-items: start;
  gap: 10px 18px;
}
.t4g-general-grid .field { min-width: 0; max-width: none; width: 100%; flex: none; }
/* 2026-08-04, Desktop Responsive System spec (closes an audit FAIL - this
   grid had zero responsive coverage at any breakpoint). The 4 fixed-content
   columns need >=654px combined (160+150+150+140+3*18px gaps) - measured
   overflow starting around ~1215px effective viewport (real on a
   1366x768/1920x1080 panel under Windows display scaling). Below that,
   wrap to a simple 2x2 grid instead of squeezing the deliberately-sized
   160px/140px columns (their exact widths are load-bearing - see the
   comment above this rule - so they aren't touched, just given more room
   via wrapping). 1280px picked with headroom above the measured ~1215px
   overflow point. */
@media (max-width: 1280px) {
  .t4g-general-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.t4g-port-stack { display: flex; flex-direction: column; gap: 10px; min-width: 0; }

/* ===== Baseband Bridge redesign ============================================
   Source / Target two-column layout with a subtle divider; each bridge is
   its own card; generated names carry an inline Customize (pencil).
   Everything below is scoped under .bb-page so this page's tightened
   spacing never touches the shared .card/.grid rules other pages rely on. */
.bb-page .card { padding: 8px 12px 10px; margin-bottom: var(--sp-2); }
.bb-page .card > .card-title { margin-bottom: 6px; padding-bottom: 6px; }
/* Every stacked field row inside a bridge card (Technology/Network/VLAN ID,
   Source/Target VLAN Name, Bridge Name) is its own .grid block; consecutive
   .grid siblings carry no default margin, so without this they touch with
   zero vertical gap. */
.bb-page .bb-bridge-card > .grid + .grid { margin-top: var(--sp-2); }

/* Field height unification: native <select> renders ~1px taller than a
   plain text <input> even with identical padding/border (UA default combo-
   box metrics), which throws off every row that mixes the two (Port
   Configuration, generated-field rows). Same fix as Router's dropdown
   pass: drop the native chevron and drive height off explicit box metrics
   so select/input/generated-input are pixel-identical everywhere on this
   page. */
.bb-page select,
.bb-page input[type=text] {
  height: 32px; padding: 0 10px; line-height: 30px; box-sizing: border-box;
}
.bb-page select {
  -webkit-appearance: none; -moz-appearance: none; appearance: none; cursor: pointer;
  padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239FB0C2' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
}
.bb-page select:hover,
.bb-page select:focus {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2316a394' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}

.bb-split { display: flex; align-items: stretch; gap: var(--sp-3); }
.bb-col { flex: 1 1 0; min-width: 0; }
.bb-col-head {
  font-size: 0.6875rem; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  color: var(--accent); margin-bottom: var(--sp-2);
}
/* Column contents stack one field per row (rather than wrap), so Source and
   Target - and per-bridge Source VLAN / Target VLAN - line up field-for-
   field on the same baseline regardless of how many fields each column has
   (Target carries one extra row: Ethernet Speed). Fields fill the column's
   full width - an engineering form reads left-to-right against a real
   column edge, not as an island of controls floating in open space. */
.bb-col-grid { display: flex; flex-direction: column; gap: 7px; }
.bb-col-grid .field { max-width: none; width: 100%; min-width: 0; flex: none; }
.bb-divider { flex: 0 0 1px; width: 1px; align-self: stretch; background: var(--border); }

/* Fixed-track grids (replace the default flex-wrap .grid for these two rows).
   Technology/Network get a fixed "medium" column (their values are only ever
   a handful of characters); VLAN ID takes 1fr - all remaining row width - so
   it reads as the dominant field and the row fills the card edge-to-edge
   with no leftover gap on the right. */
.bb-row-tn { display: grid; grid-template-columns: 160px 140px minmax(160px, 1fr); gap: 10px 12px; }
/* Source / Target VLAN Name split the row evenly and fill it completely. */
.bb-row-names { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; }
.bb-row-tn .field, .bb-row-names .field { max-width: none; width: 100%; min-width: 0; flex: none; }
/* Bridge Name anchors the card: full row width (.field.wide already gives it
   flex:1 1 100%), left-aligned like every other row - no width override. */

/* Monospace for literal engineering values (ports, VLAN IDs, generated
   names) - visually distinguishes exact identifiers from ordinary dropdown
   words (Technology/Network/Speed) and keeps look-alike glyphs unambiguous. */
.bb-page input.mono, .bb-page select.mono { font-family: var(--mono); letter-spacing: .2px; }

.bb-bridges-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-2); }
.bb-section-label {
  font-size: 0.6875rem; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  color: var(--accent);
}

.bb-bridge-card { position: relative; }
.bb-bridge-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 6px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
.bb-bridge-head .card-title { margin: 0; padding: 0; border: none; }
.bb-remove {
  width: 26px; height: 26px; flex: 0 0 auto; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: transparent; color: var(--muted);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.bb-remove:hover { border-color: var(--error); color: var(--error); }
.bb-remove .ic { width: 14px; height: 14px; }

/* Generated name + inline Customize - one shared look for every instance
   (Ethernet Port, Source/Target VLAN Name, Bridge Name): same size,
   position, icon, padding, radius and hover as .bb-edit below. A dashed
   border marks "this value is derived" (distinct from both a plain input
   and a disabled one); it turns into a solid accent border the moment the
   user Customizes it, so auto vs. manual is legible at a glance. */
.bb-gen-wrap { display: flex; align-items: center; gap: 5px; }
.bb-gen-input { flex: 1 1 auto; min-width: 0; }
input.bb-gen-input { font-family: var(--mono); letter-spacing: .2px; }
.bb-gen-input[readonly] {
  color: var(--text-2); background: var(--panel2); cursor: default;
  border-style: dashed; border-color: var(--border-2);
}
.bb-gen.editing .bb-gen-input {
  color: var(--text-value); background: var(--panel2);
  border-style: solid; border-color: var(--accent);
}
.bb-edit {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: transparent; color: var(--muted);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.bb-edit:hover { border-color: var(--accent); color: var(--accent); }
.bb-gen.editing .bb-edit { border-color: var(--accent); color: var(--accent); }
.bb-edit .ic { width: 14px; height: 14px; }

.bb-bridges .bb-bridge-card:last-child { margin-bottom: 0; }
.bb-add .ic { width: 14px; height: 14px; }

/* Port Configuration header: "Add 2nd 5G Node Bridge" toggle - secondary
   outline button (.btn.ghost.sm) that flips to a selected/added look, same
   accent-wash treatment as .bb-edit/.bb-gen.editing elsewhere on this page.
   Visual-only toggle for now (see main.js SECTIONS.bridge). */
.bb-g5-toggle .ic { width: 14px; height: 14px; }
.bb-g5-toggle { transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast); }
.bb-g5-toggle.active {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent);
}
.bb-g5-toggle.active:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }

@media (max-width: 768px) {
  .bb-split { flex-direction: column; gap: var(--sp-3); }
  .bb-divider { width: auto; height: 1px; align-self: stretch; }
  .bb-row-tn, .bb-row-names { grid-template-columns: 1fr; }
}

/* Baseband Bridge only: reclaim a little preview-panel height for more
   visible generated code (header/status rows shared by every page, so the
   reduction is scoped via :has() to this page alone). */
.content:has(.bb-page) .preview-head { margin-bottom: 6px; }
.content:has(.bb-page) #preview-status { min-height: 30px; padding: 0 12px; }

/* ===== Link Migration - DUW Node Identity/Sync/RAN/OAM redesign ===========
   Compact enterprise layout: two-column rows wherever a section naturally
   pairs (Iub ID/RBS ID, Ref-7/8 address+domain, RAN/OAM IP+Gateway), a
   single-field row for the odd one out (User Label, VLAN). Scoped to
   .lm-page (added only in DUW mode - see main.js) so DUS mode's plain
   addCard() layout is untouched. */
.lm-page .card { padding: 10px 14px 12px; margin-bottom: 10px; }
.lm-page .card > .card-title { margin-bottom: var(--sp-2); padding-bottom: var(--sp-2); }
/* Stacked row blocks (lm-row2/lm-row1) are separate .grid siblings with no
   default margin between them - same zero-gap issue as any stacked .grid
   pair, closed here for every combination. */
.lm-page .card > * + * { margin-top: 10px; }

.lm-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; }
.lm-row1 { display: grid; grid-template-columns: 1fr; }
/* VLAN fields (a handful of digits) don't need the full row width - about a
   quarter of the card. */
.lm-row1-narrow { grid-template-columns: 170px; }
/* A field that needs more room than a VLAN but not the full row (e.g. an
   IkePeer identifier with a command block underneath) - a bit short of the
   full row width. */
.lm-row1-75 { grid-template-columns: 82%; }
/* 4G Logical IP / RAN VLAN paired row: VLAN doesn't need half the row, so
   it gets a fixed narrow column and Logical IP takes what's left. */
.lm-row2-vlan { grid-template-columns: 1fr 170px; }
/* DUS RAN Configuration: RAN IP / Gateway / 4G Logical IP / RAN VLAN all on
   one line - the three IP-ish fields are 20% narrower than an even 1fr
   split would give them, and VLAN's column is widened by that same freed
   amount (110px -> 217px). */
.lm-row4 { display: grid; grid-template-columns: 1fr 1fr 1fr 217px; gap: 10px 12px; }
/* IP / Gateway / VLAN all on one line (DUS OAM, and DUW RAN + OAM - same
   box sizing shared across all three so they read as one system), with the
   20%-to-VLAN reallocation (110px -> 220px). */
.lm-row3 { display: grid; grid-template-columns: 1fr 1fr 220px; gap: 10px 12px; }
/* Three equal-width fields on one line, no narrow column (Node Identity:
   Iub ID / RBS ID / User Label - all the same kind of field). */
.lm-row3-eq { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px 12px; }
/* OAM Configuration's row wrapper (toggled hidden/shown as a whole) - its
   own vertical rhythm since it's nested one level deeper than the card's
   direct-child "* + *" spacing rule above. */
.lm-oam-body { display: flex; flex-direction: column; gap: 10px; }
.lm-row2 .field, .lm-row1 .field, .lm-row4 .field, .lm-row3 .field, .lm-row3-eq .field { max-width: none; width: 100%; min-width: 0; flex: none; }

/* Fixed literal prefix (e.g. "Iub_") glued to its input - the user only
   types the variable suffix; the builder prepends the same literal
   server-side (linkmig_builder._build_duw). Not an input, not editable. */
.lm-prefix-wrap { display: flex; align-items: stretch; }
.lm-prefix {
  flex: 0 0 auto; display: flex; align-items: center;
  padding: 0 8px; background: var(--panel2); color: var(--text-2);
  border: 1px solid var(--border); border-right: none;
  border-radius: var(--r-md) 0 0 var(--r-md);
  font-family: var(--mono); font-size: 0.78125rem; font-weight: 500;
}
.lm-prefix-wrap input { border-radius: 0 var(--r-md) var(--r-md) 0; }

/* Reference "get ..." command block under Node Identity / Synchronization
   fields - same visual language as the generated-code Preview panel
   (--preview-bg/--preview-fg/--mono) so it reads as "this is a command",
   not another input. */
.lm-cmd {
  display: flex; align-items: center; gap: 6px;
  margin-top: 5px; padding: 6px 8px;
  background: var(--preview-bg); color: var(--preview-fg);
  border: 1px solid var(--border); border-radius: var(--r-sm);
}
.lm-cmd-text {
  flex: 1 1 auto; min-width: 0; overflow-x: auto; white-space: nowrap;
  font-family: var(--mono); font-size: 0.6875rem; line-height: 1.4;
}
.lm-cmd-copy {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: transparent; color: var(--muted);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.lm-cmd-copy:hover { border-color: var(--accent); color: var(--accent); }
.lm-cmd-copy.copied { border-color: var(--ok); color: var(--ok); }
.lm-cmd-copy .ic { width: 12px; height: 12px; }

@media (max-width: 768px) {
  .lm-row2 { grid-template-columns: 1fr; }
  .lm-row4 { grid-template-columns: 1fr 1fr; }
  .lm-row3 { grid-template-columns: 1fr; }
  .lm-row3-eq { grid-template-columns: 1fr; }
}

/* DUS Script - "Change Finger Print" toggle shares a grid row with the
   Number of Sectors select; a blank spacer label (main.js) pushes the
   check-row down to the select box's own level instead of the label row,
   and it sizes to its own content instead of the shared 200px field cap. */
.dus-fp-field { max-width: none; flex: 0 0 auto; }
/* margin-top nudges the switch down off the blank spacer label so it's
   vertically centered on the select/input boxes beside it, not the label
   row above them (explicit user request, measured delta: the switch's own
   center sat ~21px above the select/input center). */
.dus-fp-field .check-row { padding: 0; margin-top: 21px; }
/* "Change Finger Print" label: -10% font-size (15px -> 13.5px), bold -> regular
   (explicit user request, this field's label only - the shared .check-row
   label used elsewhere, e.g. 2G's "Combine Cell", is untouched). */
.dus-fp-field .check-row label { font-size: 13.5px; font-weight: 400; }

/* DUS Script General row rebalance (explicit user request): Number of
   Sectors only needs to show 1-12, Site ID is a short code, Site Name is
   the long free-text field - so the row is fixed-width per field instead
   of the shared equal-grow .field sizing, and never wraps. */
.dus-general-row { flex-wrap: nowrap; }
/* Field sizes to its own label ("Number of Sectors" doesn't fit in a
   narrowed box without wrapping to 2 lines and breaking row alignment -
   same auto-size approach .dus-fp-field already uses) while the select
   control itself shrinks -10% (explicit user request) from its old
   ~171px width, since it only ever shows 1-12. */
.dus-general-row .dus-numsec-field { flex: 0 0 auto; min-width: 0; max-width: none; }
.dus-general-row .dus-numsec-field select { width: 154px; }
.dus-general-row .dus-fp-siteid { flex: 0 0 120px; min-width: 0; max-width: 120px; }
.dus-general-row .dus-fp-sitename { flex: 0 0 240px; min-width: 0; max-width: 240px; }

/* ---------- Baseband External Alarms ---------- */
.aea-page { display: flex; flex-direction: column; gap: var(--sp-3); }
.aea-table { display: flex; flex-direction: column; gap: var(--sp-2); }
.aea-row {
  display: grid; grid-template-columns: max-content 1fr 40px; gap: var(--sp-2); align-items: center;
}
.aea-head {
  font-size: 0.75rem; font-weight: 700; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em; padding-bottom: var(--sp-1); border-bottom: 1px solid var(--border);
}
/* Shrinks to content (default text size), with a bit of extra horizontal
   padding so the box reads a little wider than bare shrink-to-fit. */
.aea-port-select { width: auto; padding: 7px 14px; }
.aea-row-del {
  display: flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  border-radius: var(--r-md); border: 1px solid var(--border); background: var(--panel);
  color: var(--muted); cursor: pointer; transition: color var(--t-fast), border-color var(--t-fast);
}
.aea-row-del:hover { border-color: var(--error); color: var(--error); }
.aea-row-del .ic { width: 16px; height: 16px; }
.aea-alarm-box {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 6px 8px;
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel);
  min-height: 40px; position: relative;
}
.aea-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.aea-chip {
  display: inline-flex; align-items: center; gap: var(--sp-1); font-size: 0.75rem; font-weight: 600;
  padding: 3px 6px 3px 10px; border-radius: var(--r-pill);
  background: var(--accent-soft); border: 1px solid var(--accent); color: var(--text-value);
}
.aea-chip-remove {
  display: flex; align-items: center; justify-content: center; width: 16px; height: 16px;
  border: none; background: none; color: inherit; cursor: pointer; opacity: .75; padding: 0;
}
.aea-chip-remove:hover { opacity: 1; color: var(--error); }
.aea-chip-remove .ic { width: 12px; height: 12px; }
.aea-alarm-input-wrap { position: relative; flex: 1 1 140px; min-width: 140px; }
.aea-alarm-input {
  width: 100%; border: none; background: none; outline: none; font: inherit;
  color: var(--text); padding: 4px 2px;
}
.aea-alarm-dropdown {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: var(--z-popover);
  max-height: 330px; overflow-y: auto; background: var(--panel);
  border: 1px solid var(--glass-border); border-radius: var(--r-md);
  box-shadow: var(--sh-2); padding: 4px;
}
.aea-alarm-option { padding: 6px 10px; border-radius: var(--r-sm); font-size: 0.8125rem; cursor: pointer; color: var(--text); }
.aea-alarm-option:hover, .aea-alarm-option.hl { background: var(--accent-soft); color: var(--text-value); }
.aea-alarm-option.is-fav { padding-left: 26px; position: relative; }
.aea-alarm-option.is-fav::before {
  content: ""; position: absolute; left: 10px; top: 50%; width: 6px; height: 6px;
  transform: translateY(-50%); border-radius: var(--r-pill); background: var(--accent);
}
.aea-group-header {
  display: flex; align-items: center; gap: 6px; padding: 6px 10px; cursor: pointer;
  font-size: 0.75rem; font-weight: 700; color: var(--muted); text-transform: uppercase;
  letter-spacing: .03em; border-radius: var(--r-sm);
}
.aea-group-header:hover { background: var(--accent-soft); color: var(--text-value); }
.aea-group-header .ic { width: 14px; height: 14px; }
/* Search-result group labels are informational only (group already expanded,
   no chevron) - no pointer cursor or hover highlight. */
.aea-group-header--static { cursor: default; padding-top: 8px; }
.aea-group-header--static:hover { background: none; color: var(--muted); }
.aea-fav-chevron { display: inline-flex; transition: transform var(--t-fast); }
.aea-fav-chevron.expanded { transform: rotate(90deg); }
.aea-alarm-divider { height: 1px; background: var(--border); margin: 4px 6px; }
.aea-alarm-result-count {
  padding: 4px 10px 8px; font-size: 0.6875rem; font-weight: 600; color: var(--muted);
  border-bottom: 1px solid var(--border); margin-bottom: 4px;
}
.aea-add-port { margin-top: var(--sp-1); align-self: flex-start; }
.aea-empty-hint { font-size: 0.8125rem; color: var(--muted); padding: var(--sp-3) 0; text-align: center; }
/* .field--error input already reddens the port input generically (shared
   validation CSS); the alarm cell's search input has no visible border of
   its own, so redden the chip box that actually frames it instead. */
.aea-col-alarms.field--error .aea-alarm-box { border-color: var(--error-2) !important; }

@media (max-width: 768px) {
  .aea-row { grid-template-columns: max-content 1fr 36px; }
}

/* ---------------------------------------------------------------------
   5G active Advance - exclusive Sector Navigator / collapsible-sector UI
   (2026-07-31, per 5G_Active_Advance_Exclusive_Sector_UI_UX_Modification.md).
   Every selector below is prefixed .adv- and used ONLY by
   buildAdvSectorUI()/makeAdvSector()/makeAdvIru() in main.js - 5G Active's
   own Active Configuration pane (.sector-card etc., further up this file)
   is untouched.
   --------------------------------------------------------------------- */
.adv-nav { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.adv-nav-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  min-width: 92px; padding: var(--sp-2) var(--sp-3);
  background: var(--glass); border: 1px solid var(--glass-border);
  border-radius: var(--r-md); cursor: pointer; text-align: left;
  font-family: inherit; color: var(--text);
  transition: border-color var(--t-fast), background var(--t-fast);
}
.adv-nav-card:hover { border-color: var(--border-2); background: var(--glass-hover); }
.adv-nav-card.active {
  border-color: var(--accent); border-width: 2px; background: var(--surface-4);
}
.adv-nav-card.error { border-color: var(--error-2); }
.adv-nav-name { font-weight: 700; font-size: 0.875rem; }
.adv-nav-state {
  display: flex; align-items: center; gap: 4px; font-size: 0.6875rem; color: var(--muted);
}
.adv-nav-card.active .adv-nav-state { color: var(--accent); font-weight: 600; }
.adv-nav-card.ready .adv-nav-state { color: var(--ok); }
.adv-nav-card.error .adv-nav-state { color: var(--error-2); }
.adv-nav-state .ic { width: 13px; height: 13px; }

/* Enterprise Left Panel spacing system (2026-08-03, per 03_Spacing_System.md
   - scale is 4/8/12/16/20/24/32/40px only, no other values). Sector = Level 1
   / "Outer Card" = 24px padding; IRU/Antenna Unit Group/Sector Equipment
   Function = Level 2 / "Nested Cards" = 20px; Link/Branch row = Level 3 /
   "Compact Cards" = 16px - each level lighter than its parent, per
   05_Enterprise_UX_Rules.md "Nested cards must have lighter visual emphasis
   than parent cards". Accordion headers get a 48px minimum height with
   vertically-centered chevron/title/actions (03_Spacing_System.md). */
/* 2026-08-04, FINAL pass: anchored directly to BB RRU Creator's own,
   already-shipped spacing system (.tb-sector/.tb-sector-head/.tb-block/
   .tb-radio in this same file, ~line 1483) instead of more hand-picked px
   values - "that page is already right, copy it" (explicit user
   direction). Every value below is a real value copied from those rules,
   not invented for this page. */
.adv-sector-card { padding: var(--sp-4); } /* = .tb-sector padding (16px), when OPEN (real content card) */
/* Collapsed row (2026-08-04, explicit user feedback: still too tall for a
   one-line summary row even after the earlier divider-padding fix) - the
   full 16px card padding makes sense once there's real content inside,
   but a collapsed row is just a clickable header, so it gets a tighter
   vertical padding instead (horizontal 16px kept, no width change). */
.adv-sector-card:not(.adv-sector-open) { padding: 4px var(--sp-4); }
.adv-sector-head {
  display: flex; align-items: center; gap: 12px; cursor: pointer;
  padding: 0; min-height: 38px; user-select: none; /* kept - explicit "do not reduce further" */
}
.adv-sector-chev { display: inline-flex; color: var(--muted); transition: transform var(--t-fast); }
.adv-sector-chev.adv-chev-open { transform: rotate(90deg); }
.adv-sector-head-title { font-weight: 600; font-size: 0.875rem; color: var(--text-heading); }
/* 2026-08-04, real bug fix (user caught it from a screenshot of the
   COLLAPSED card): padding-bottom(12)/margin-bottom(8) - the divider gap -
   were unconditional on .adv-sector-head, so a collapsed sector (body
   display:none, no divider rendered at all) still reserved ~20px of dead
   space under its single line of text. Moved both into this same
   .adv-sector-open-scoped rule that already gates the divider border
   itself, so collapsed cards are exactly as tall as their one text line +
   the card's own 16px padding - nothing more. */
.adv-sector-open > .adv-sector-head {
  padding-bottom: var(--sp-3); margin-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border);
}
/* Head now owns the divider->content gap via its own margin-bottom above
   (matching .tb-sector-head exactly) - body no longer needs its own
   top padding on top of that. Bottom padding relies on the card's own
   16px (.adv-sector-card), same as .tb-sector's content sitting flush
   inside its own padding, no separate body wrapper padding in BB either. */
.adv-sector-body { padding: 0; }
/* Sector Name + IRUs share one row now (main.js) - gap before the next
   group of content (IRU cards). Also doubles as the "first IRU controls ->
   first Link Card" gap inside makeAdvIru()'s linkArea, same class. Value
   = .tb-block:first-of-type's margin-top (12px) - BB's own "first block in
   its container" gap. */
.adv-field-group { margin-top: var(--sp-3); }

/* Generic collapse chevron (2026-08-03) - reused by the IRU/Antenna-Unit-
   Group/Multicast-Antenna-Branch/Sector-Equipment-Function cards
   (makeCollapseChevron() in main.js). Same rotate-on-open language as
   .adv-sector-chev above, but its own class since it's a dedicated
   button (not the whole header, which already has rename/delete clicks
   of its own to not conflict with). */
.adv-collapse-chev {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 16px; height: 16px; padding: 0; margin-right: 0;
  border: none; background: transparent; color: var(--muted); cursor: pointer;
  transition: color var(--t-fast), transform var(--t-fast);
}
.adv-collapse-chev:hover { color: var(--accent); }
.adv-collapse-chev.adv-chev-open { transform: rotate(90deg); }
.adv-collapse-chev .ic { width: 14px; height: 14px; }

/* Editable IRU name (2026-07-31, per 5G_Active_Advance_Editable_IRU_Name.md):
   a small inline pencil beside the IRU card title - no new field row, no
   extra height, same muted->accent hover language as .bb-edit elsewhere in
   this app but sized down to sit flush in a title line instead of a
   32x32 bordered button. */
/* Nested Card padding + card-to-card margin (2026-08-03,
   03_Spacing_System.md: 20px padding "Nested Cards", 24px "Card to Card")
   - overrides the shared .card default (12/14/14px) for this page's IRU
   cards only; min-height 48px keeps the header row (chevron/name/edit
   pencil) consistent with every other accordion header on this page. */
/* = .tb-radio padding (12px) - BB's own "Nested Card" value. margin-bottom
   = .tb-radio-row's own gap (12px) between sibling radio cards. */
.adv-iru-card { padding: var(--sp-3); margin-bottom: var(--sp-3); }
.adv-iru-title { gap: 12px; min-height: 34px; } /* height kept - explicit "do not reduce further" */
/* IRU Header -> first controls row = .tb-radio-head's own margin-bottom
   (8px). Targets the same <h3 class="card-title adv-iru-title"> the base
   .card>.card-title rule sets margin-bottom:8px on - same specificity (2
   classes), later in file so it wins for just this property; everything
   else (border-bottom color, padding-bottom/divider gap) stays untouched. */
.adv-iru-card > .card-title { margin-bottom: var(--sp-2); }
/* No 1:1 BB equivalent for a 3rd nesting level (BB's structure is flatter),
   so this stays the already-restored, non-cramped 8px/8px (padding/
   card-to-card margin) rather than an invented BB-style value - explicit
   user correction after Phase 2's 6px read as too tight. Card WIDTH
   untouched. */
.adv-link-card { padding: 8px; margin-bottom: 8px; }
/* Link Header -> controls: unified with the same 8px "header->content"
   value BB uses everywhere (.tb-sector-head/.tb-radio-head margin-bottom)
   for one consistent rhythm across all 3 header levels on this page. */
/* padding-bottom 0 -> 3px -> 4px (2026-08-04, two explicit user requests:
   "+3%" then "+1% more" on the Link title -> divider line gap). */
.adv-link-card > .card-title { padding-bottom: 4px; margin-bottom: var(--sp-2); }
.adv-iru-edit {
  flex: 0 0 auto; width: 18px; height: 18px; padding: 0; border: none;
  background: transparent; color: var(--muted); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: color var(--t-fast);
  /* Blueprint 01/04: action icons aligned to the header's right edge. */
  margin-left: auto;
}
.adv-iru-edit:hover { color: var(--accent); }
.adv-iru-edit .ic { width: 13px; height: 13px; }
.adv-iru-name-input {
  font: inherit; color: inherit; background: transparent;
  border: none; border-bottom: 1px dashed var(--accent);
  padding: 0 0 1px; min-width: 60px; max-width: 240px;
}
.adv-iru-name-input:focus { outline: none; }

/* Label-to-control gap, 5G active Advance only, scoped via data-section
   (set on every navigation, see showPage()) so it never leaks to another
   page. 2026-07-31: tightened to 3px (Micro UI Polish request).
   2026-08-03 SUPERSEDED by the Enterprise Left Panel Layout spacing
   system (03_Spacing_System.md, "Label to Control: 8px", "No
   exceptions") - the 3px value predates that formal spec and is no
   longer the intended density for this page. */
/* The earlier "leave the right X% empty" padding-right hack (65%->75%->60%,
   2026-08-03) is superseded now that the emptied strip has real content: the
   page uses the same .tb-wrap/.tb-workspace/.tb-summary two-column split as
   BB RRU Creator (see buildAdvSectorUI() in main.js) - a fixed 300px sticky
   "Topology Summary" column, with the shared .tb-wrap responsive rule
   (style.css ~1529) collapsing it to one column under 1024px for free. */
/* 2026-08-04: rescoped from [data-section="5g_active_advance"] to .adv-pane
   - this UI moved from being its own top-level section/page to a tab pane
   nested inside SECTIONS["5g_active"], so data-section is "5g_active" for
   all three of that section's tabs now and would no longer match here.
   .adv-pane is the dedicated wrapper class on this tab's own pane div (see
   SECTIONS["5g_active"].build in main.js), so this still only affects the
   Advance tab, never Active Configuration or DOT Deletion. */
.adv-pane .field { gap: var(--field-gap-lg); }
/* Field-to-field gap (2026-08-03, 03_Spacing_System.md: 16px - the shared
   .grid default of 10px/12px isn't on the allowed 4/8/12/16/20/24/32/40
   scale). Scoped the same way, tab-only. */
.adv-pane .grid { gap: 12px; }
/* Outer Card padding (2026-08-03, 03_Spacing_System.md: 24px) for the
   page's own top-level "Sectors" card - same level as the Sector card
   itself, so it gets the same padding. */
.adv-top-card { padding: 14px; }

/* Antenna Unit Group / Multicast Antenna Branch cards - UI-ONLY
   PROTOTYPE (2026-07-31, v2 per
   5G_Active_Advance_AntennaUnitGroup_Enterprise_UI_v2.md - compact
   enterprise redesign of the v1 layout from
   5G_Active_Advance_Antenna_Unit_Group_UI_Redesign.md). See the matching
   comment above makeAdvAntennaGroups() in main.js: disconnected from
   Generate/backend on purpose, same precedent as the retired iru_test
   experiment. Nested one level deeper than .adv-sector-card/.adv-iru-card
   (smaller title, tighter padding) to read as "inside" an IRU/Sector
   rather than a sibling of equal weight. */
/* 2026-08-04, FINAL fix (explicit user rule: NO negative margins, find the
   real cause): the previous -22px hack was compensating for a genuine bug
   - .adv-iru-card's own margin-bottom (12px, meant to space IRU-A-1 from
   IRU-A-2) was ALSO landing after the LAST IRU card, stacking with this
   wrap's own margin-top for no reason (nothing needs 12px + this value
   between the last IRU and the next section - that 12px only makes sense
   BETWEEN two IRU cards). Removing that redundant trailing margin at the
   real source instead of patching around it with a negative number. */
.adv-iru-card:last-child { margin-bottom: 0; }
/* Same redundant-trailing-margin fix, one level down: a Link Card's own
   margin-bottom only makes sense before a sibling Link 2 - when it's the
   only/last link, that margin was stacking uselessly against the IRU
   card's own bottom padding right after it. */
.adv-link-card:last-child { margin-bottom: 0; }
/* Major Section Gap = BB RRU Creator's own .tb-block (non-first) margin-top
   (20px, var(--sp-5)) - "that page is already right, copy it" (explicit
   user direction), replacing the earlier hand-picked 16px/24px guesses. */
.adv-aug-wrap { margin-top: var(--sp-5); padding-top: var(--sp-2); border-top: 1px dashed var(--border); }
.adv-aug-title { font-size: 0.75rem; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }
/* Title row wrapper (2026-08-04): holds the existing uppercase section
   label on the left and the new per-section "Include in output" checkbox
   on the right - inherits the same margin-bottom the title used to carry
   on its own, so section-to-content spacing is unchanged. */
.adv-aug-title-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: var(--sp-2); padding-right: 2px; }
.adv-include-toggle { display: flex; align-items: center; gap: 6px; font-size: 0.7rem; font-weight: 500; color: var(--muted); text-transform: none; letter-spacing: normal; cursor: pointer; user-select: none; }
/* Plain small square checkbox (not the app's usual pill toggle switch) -
   the global input[type=checkbox] rule (~line 469) draws an 18px round
   thumb via ::before regardless of the input's own width/height, so a
   naive width/height override alone still rendered full-size; this fully
   replaces the toggle-switch look with a small tick box instead. */
.adv-include-toggle input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  position: relative; width: 13px; height: 13px; margin: 0; padding: 0;
  border: 1px solid var(--border-2); border-radius: 3px; background: transparent;
  box-shadow: none; cursor: pointer; transition: background 150ms var(--ease-out), border-color 150ms var(--ease-out);
}
.adv-include-toggle input[type="checkbox"]::before { content: none; }
.adv-include-toggle input[type="checkbox"]:checked { background: var(--accent); border-color: var(--accent); }
.adv-include-toggle input[type="checkbox"]:checked::after {
  content: ""; position: absolute; left: 3px; top: 0px;
  width: 4px; height: 7px; border: solid var(--on-accent); border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
/* Card-to-card between sibling Antenna Unit Group cards / Sector Equipment
   Function entries - compacted (user request: tighter, denser page). */
.adv-aug-list { display: flex; flex-direction: column; gap: 12px; }

/* Nested Card padding (2026-08-03, 03_Spacing_System.md: 20px) - Level 2
   in the hierarchy (Antenna Unit Group / Sector Equipment Function),
   lighter than the Sector's own 24px "Outer Card" padding. */
.adv-aug-card {
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel2, var(--panel));
  padding: 14px; transition: opacity var(--t-fast), transform var(--t-fast);
}
.adv-aug-card.adv-aug-enter, .adv-aug-card.adv-aug-leave { opacity: 0; transform: translateY(-4px); }
/* v2: header reads as a proper divider (same padding-bottom/border-bottom
   convention as .card-title elsewhere) so it visually matches the IRU
   section's title treatment, then the branch list sits right under it -
   net effect: header pulled ~8-12px closer to the content below versus
   the plain no-divider v1 header. 48px min-height + vertical centering
   (2026-08-03, 03_Spacing_System.md accordion rule) added on top. */
.adv-aug-head {
  display: flex; align-items: center; gap: 0; min-height: 44px;
  padding-bottom: 0; margin-bottom: 12px; border-bottom: 1px solid var(--border);
}
/* Header icon spacing (blueprint 01/04): chevron 12px before the title,
   then edit + 8px + delete grouped on the right. gap is 0 here because a
   single flex gap can't be both 12 (chevron->title) and 8 (edit->delete). */
.adv-aug-head .adv-collapse-chev,
.adv-aug-branch-head .adv-collapse-chev { margin-right: 12px; }
/* Prefix + value sit flush (zero gap) as one visual unit
   "antennaUnitGroup=1" - only .adv-aug-value is ever clickable/editable,
   the "antennaUnitGroup=" prefix is inert text. */
.adv-aug-name-group { display: inline-flex; font-weight: 600; font-size: 0.8125rem; }
.adv-aug-prefix { color: var(--text-heading); }
.adv-aug-value { color: var(--text-heading); cursor: pointer; }
.adv-aug-value:hover { color: var(--accent); }
.adv-aug-value-input {
  font: inherit; font-weight: 600; color: inherit; background: transparent;
  border: none; border-bottom: 1px dashed var(--accent); padding: 0 0 1px; width: 2.5em;
}
.adv-aug-value-input:focus { outline: none; }
.adv-aug-edit, .adv-aug-del, .adv-aug-branch-del {
  flex: 0 0 auto; width: 18px; height: 18px; padding: 0; border: none; background: transparent;
  color: var(--muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: color var(--t-fast);
}
/* Blueprint 01/04: action icons grouped on the header's right edge, edit
   then 8px then delete. The edit pencil is pushed right (margin-left:auto),
   delete follows it 8px later. */
.adv-aug-edit { margin-left: auto; }
.adv-aug-del { margin-left: 8px; }
.adv-aug-edit:hover { color: var(--accent); }
.adv-aug-del:hover, .adv-aug-branch-del:hover { color: var(--error-2); }
.adv-aug-edit .ic, .adv-aug-del .ic, .adv-aug-branch-del .ic { width: 13px; height: 13px; }

/* Card-to-card between sibling Multicast Antenna Branch rows - compacted. */
.adv-aug-branch-list { display: flex; flex-direction: column; gap: 12px; }
/* 05_Multicast_Branch_Blueprint_v2.md: card padding 20px (all sides),
   header 52px, header -> Active IRUs 16px, Active IRUs label -> buttons
   8px, buttons -> divider 16px, IRU row height 52px, 1px row dividers. */
.adv-aug-branch-row {
  display: flex; flex-direction: column; gap: 0; padding: 14px;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  transition: opacity var(--t-fast), transform var(--t-fast);
}
.adv-aug-branch-row.adv-aug-enter, .adv-aug-branch-row.adv-aug-leave { opacity: 0; transform: translateY(-4px); }
.adv-aug-branch-head {
  display: flex; align-items: center; min-height: 44px;
  border-bottom: 1px solid var(--border);
}
.adv-aug-branch-name { font-size: 0.75rem; font-weight: 600; color: var(--text-2); }
.adv-aug-branch-del { margin-left: auto; }
/* Header -> content, compacted. The "+ Add Multicast Antenna Branch" button
   lives one level up in the owning group's body, so it isn't a flex child
   here - only the Active IRUs block and the IRU rows are. */
.adv-aug-branch-body { display: flex; flex-direction: column; margin-top: 10px; }
/* "Active IRUs" label + buttons on one line (2026-08-03, explicit user
   request - was label-above/buttons-below, now inline like a labeled
   control row). */
.adv-aug-active-irus { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.adv-aug-active-irus-label { font-size: 0.8125rem; font-weight: 700; color: var(--text-2); flex: 0 0 auto; }
.adv-aug-active-irus .port-row { margin-top: 0; gap: 10px; }
/* One row per ACTIVE (checked in "Active IRUs" above) IRU slot: name |
   "Transceiver (This Branch)" label | 1-4 buttons. Compacted (user
   request: no wide gaps, pack everything close, use the space densely) -
   left-packed flex row instead of a full-width 3-column grid. Name is a
   fixed 84px slot so rows still line up; the label and the button group
   follow immediately with a small consistent gap. */
.adv-aug-iru-rows { display: flex; flex-direction: column; }
/* Divider + gap between the Active IRUs selector and the rows appears only
   when at least one IRU row is actually active/visible (rows are
   display:none while unchecked, so :empty can't be used - :has can). */
.adv-aug-iru-rows:has(.adv-aug-iru-row:not(.hidden)) {
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);
}
/* 3-column grid (name / label / toggle), 2026-08-03 fix: the previous flex
   row only ever looked aligned because every branch happened to show the
   same "Transceiver (This Branch)" label text at the same width - any
   other content length would have shifted the toggle buttons out of line
   between IRU-A-1's row and IRU-A-2's row. A real grid guarantees every
   row's 3 zones start at the same X regardless of content, no exceptions.
   Switched from rigid 22/34/44% to content-driven tracks (2026-08-03,
   follow-up fix - fixed percentages left the toggle column wider than the
   4 buttons actually need while starving the label, causing "Transceiver
   (This Bran..." to truncate even though there was spare room next door):
   name and toggle each take exactly what their content needs (max-content),
   the label gets whatever is left, still allowed to shrink/truncate first
   when the row is genuinely too narrow (minmax(0, ...) - not the earlier
   minmax(60px, 1fr), which forced the label track to always fill 100% of
   the leftover space even when the text itself was much shorter, reading
   as a big dead gap before the toggle buttons whenever the card had room
   to spare; max-content lets the column hug the text instead). Row gap
   adds real breathing room between stacked rows (previously flush against
   the divider, read as "too close together"). */
/* 14px gap between rows (2026-08-03, final approach - see the divider rule
   below: the border used to live INSIDE the row's own box, which forced a
   padding "compensation" hack and never actually made row A and row B
   identical. Now the gap is real space between two IDENTICAL row boxes,
   and the divider is a free-floating line drawn in the middle of that gap
   via ::before - it touches neither row's box model, so there is no
   compensation to get wrong anymore.) */
.adv-aug-iru-rows { row-gap: 14px; }
/* Name column fixed at 60px (2026-08-03, root-cause fix - explicit
   user-caught bug): with max-content, "IRU-A-1" and "IRU-A-2" render at
   very slightly different widths (digit glyph shape alone, ~2px), which
   shifted the label column's available width by that same ~2px between
   the two rows. On a narrow card, where the label is already truncating,
   that 2px difference lands the ellipsis at a different character in each
   row - a real, provable pixel inconsistency, not just a rounding
   artifact. A fixed name track removes the variable entirely: every row's
   label gets the exact same available width, so truncation (if any) lands
   at the identical point in every row, always. */
.adv-aug-iru-row {
  display: grid; grid-template-columns: 60px minmax(0, max-content) max-content;
  column-gap: 10px; align-items: center; min-height: 40px; min-width: 0; padding: 7px 0;
  position: relative;
}
.adv-aug-iru-row.hidden { display: none; }
.adv-aug-iru-row-name {
  font-size: 0.75rem; font-weight: 700; color: var(--text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The label is the only shrinkable part: the content-sized name/toggle
   tracks keep their width, so when the row narrows the label truncates
   with an ellipsis instead of pushing the toggle buttons out of alignment
   (title attribute on the element, set in main.js, shows the full text on
   hover when that happens). */
.adv-aug-iru-tx-label {
  min-width: 0; color: var(--accent); font-weight: 700;
  font-size: 0.75rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.adv-aug-iru-row .tx-field { min-width: 0; }
/* Toggle container/buttons - compact selection group (2026-08-03, tightened
   per explicit user feedback: the first 34x34 pass still read as "numbers
   too close and too big" - shrunk both the button box and the digit size,
   with a touch more gap so the 4 toggles read as distinct, organized
   buttons instead of one crowded bar. Scoped to this row only so 5G
   Active's own Transceiver (this sector) control (same .tx-field .port-row
   /.port-check classes, elsewhere) is untouched. */
.adv-aug-iru-row .tx-field .port-row { margin-top: 0; padding: 3px; gap: 6px; }
.adv-aug-iru-row .tx-field .port-check { width: 26px; height: 26px; padding: 0; font-size: 0.6875rem; }
/* "Active IRUs" toggle chips (2026-08-03, corrected per explicit user
   feedback: the previous 700-weight/flush-height version read as
   inconsistent with the rest of the app's chips - reverted font-weight
   to the base .port-check's own 600 and sized padding off the same
   6px/12px family other .port-check chips already use everywhere else,
   just a touch roomier for a primary selector). Scoped to this row so the
   generic .port-check base used elsewhere on the page (RF port pickers
   etc.) is untouched. */
/* Shrunk back down (2026-08-03, follow-up: the 13px/6x14 pass read as too
   dominant/oversized next to the "Active IRUs" label) - label stays 13px/
   700 for emphasis, chips drop to the base .port-check's own 12px/600 with
   tighter padding, so the pair reads as one balanced control again. */
.adv-aug-active-irus .port-check { padding: 5px 10px; }
/* Thin divider between rows only when both are active/visible - "no
   vertical separators" elsewhere, so this border is the only one.
   2026-08-03, FINAL root-cause fix (explicit user correction, twice: a
   border-top attached to row B's own box can never make row A and row B
   truly identical, no matter how the padding is tuned around it - it's
   always part of ONE row's box and not the other's, so there is always
   some residual asymmetry). Every .adv-aug-iru-row now has 100% identical
   CSS, no exceptions, guaranteeing pixel-identical dimensions for any
   number of rows. The divider itself is a free-floating ::before line,
   positioned in the exact middle of the 14px row-gap above this row -
   it's absolutely positioned, so it affects NEITHER row's box model at
   all. */
.adv-aug-iru-row:not(.hidden) + .adv-aug-iru-row:not(.hidden)::before {
  content: ""; position: absolute; left: 0; right: 0; top: -7px;
  height: 1px; background: var(--border);
}

.adv-aug-add-branch-wrap { position: relative; margin-top: 12px; }
.adv-aug-add-group, .adv-aug-add-branch {
  display: inline-flex; align-items: center; gap: 6px; background: transparent;
  border: 1px dashed var(--border-2); border-radius: var(--r-sm); color: var(--muted);
  font-size: 0.75rem; font-weight: 600; padding: 6px 10px; cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.adv-aug-add-group { margin-top: var(--sp-2); }
.adv-aug-add-group:hover, .adv-aug-add-branch:hover { color: var(--accent); border-color: var(--accent); }
.adv-aug-add-group .ic, .adv-aug-add-branch .ic { width: 13px; height: 13px; }
/* position:fixed (not absolute) + appended to document.body, positioned via
   getBoundingClientRect() in openAdvPicker() (main.js) - 2026-08-07 fix: an
   absolutely-positioned popover nested inside .adv-sector-card/.card is
   trapped inside that card's own stacking context (created by the card's
   `transform`, see style.css .card), so it could never paint above a LATER
   sibling sector card no matter its z-index. Rendering it at the body level
   sidesteps that trap entirely. */
.adv-aug-picker {
  position: fixed; z-index: var(--z-popover); min-width: 200px;
  background: var(--panel); border: 1px solid var(--glass-border); border-radius: var(--r-md);
  box-shadow: var(--sh-2); padding: 4px; display: flex; flex-direction: column; gap: 2px;
  overflow-y: auto; /* long option lists scroll within maxHeight (main.js openAdvPicker) instead of running off-screen */
}
.adv-aug-picker-opt {
  text-align: left; background: transparent; border: none; color: var(--text);
  font-size: 0.8125rem; padding: 6px 10px; border-radius: var(--r-sm); cursor: pointer;
}
.adv-aug-picker-opt:hover { background: var(--accent-soft); color: var(--text-value); }

/* Sector Equipment Function rfBranchRef chips (2026-08-03) - selected
   "antennaunitgroup=X,MulticastAntennaBranch=Y" refs, shown in exact pick
   order with a small remove "x"; the "+ Add rfBranchRef" button below
   reuses .adv-aug-add-branch/.adv-aug-picker/.adv-aug-picker-opt as-is. */
.adv-sef-refs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.adv-sef-refs:empty { margin-top: 0; }
.adv-sef-ref-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--accent-soft); color: var(--text-value);
  border-radius: var(--r-sm); padding: 4px 6px 4px 10px;
  font-size: 0.75rem; font-family: var(--font-mono, monospace);
}
.adv-sef-ref-remove {
  flex: 0 0 auto; width: 16px; height: 16px; padding: 0; border: none; background: transparent;
  color: inherit; opacity: .7; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: opacity var(--t-fast);
}
.adv-sef-ref-remove:hover { opacity: 1; }
.adv-sef-ref-remove .ic { width: 12px; height: 12px; }
