/* ============================================
   ONAIR COMPANION - MAIN STYLESHEET
   Theme: Aviation Dark - Cockpit Inspired
   ============================================ */

:root {
  /* Tells Chromium to use dark UA defaults for native form controls
     (notably the <select> option popup, scrollbars, date pickers).
     Without this, the option popup renders with light OS colors —
     white background + light grey selected-text → illegible. */
  color-scheme: dark;

  /* Core palette */
  --bg-primary: #0a0e1a;
  --bg-secondary: #111827;
  --bg-card: #1a2035;
  --bg-card-hover: #1f2847;
  --bg-elevated: #232d48;
  --bg-input: #0d1222;

  /* Row hover — ONE ink for every list family (tables, paper strips, the
     alert rows of the Industries dashboard). Painted as an extra background
     LAYER (`background-image: linear-gradient(<ink>, <ink>)`) wherever the row
     already owns its background, so the base colour — and the light-mode paper
     grain — survive the hover, and so a themed row keeps its own hue.
     6% measured, not guessed: it puts a hovered row at ~1.19-1.20 contrast
     against its background on every shipped theme. The 2% it replaces sat at
     1.055, i.e. invisible (ticket #12030, RickStratman 2026-09-16). */
  --row-hover-overlay: rgba(255,255,255,0.06);

  /* Accent */
  --accent-blue: #3b82f6;
  --accent-blue-dim: #1d4ed8;
  --accent-cyan: #06b6d4;
  --accent-green: #10b981;
  --accent-orange: #f59e0b;
  --accent-red: #ef4444;
  --accent-purple: #8b5cf6;
  --accent-pink: #ec4899;
  /* Alias — several components reference --accent-amber; resolves to the
     theme's orange at usage time (base orange IS amber-500). Defining it once
     here keeps every theme block untouched. */
  --accent-amber: var(--accent-orange);
  /* Gold — the "prize / prestige / favorite" hue (favorite-airport stars, level-up,
     leaderboard, VA equity, trading hall). 19 components already referenced
     --accent-gold while it was declared nowhere: an undefined var makes the whole
     declaration invalid, so those stars/badges silently rendered black (fill) or
     invisible (stroke: none). Same hex as the gold literal the favorites map
     markers use (fav-airports.jsx), so CSS and Leaflet agree. Deliberately fixed
     across themes, like the role colors above. */
  --accent-gold: #f5c842;
  /* Caution yellow — the "look at this" hue, distinct from the prestige gold above
     (limited operating hours, runway length callouts, ATC callsign, .dot-yellow,
     sim-setup warnings). Referenced by 12 sites while declared nowhere; the 5 that
     omitted a fallback rendered with the inherited color instead. Fixed across
     themes like the gold and role colors — caution must not drift with the skin. */
  --accent-yellow: #eab308;

  /* Role colors (Pilot / Cabin Crew / Mechanic) — fixed, theme-independent
     hues kept deliberately far apart on the wheel so the three roles stay
     clearly distinguishable everywhere they get a colored accent (employee
     strips/table, crew lists, map legends, …). Mirror the hex used by the
     JS color maps (logistic-map, pending-jobs popups) so the convention is
     identical across CSS and canvas/Leaflet renderers. */
  --role-pilot: #3b82f6;  /* blue   */
  --role-crew:  #22c55e;  /* green  */
  --role-mech:  #f97316;  /* orange */

  /* Virtual Airline — the "this belongs to your VA" hue. Fixed across themes
     for the same reason as the role colors above: it encodes an ownership
     FACT, and a fact must not change colour when the player changes skin.
     Deliberately the same cyan the VA badge in the company switcher already
     uses (screen-header.jsx), so a marked row reads as the same thing as the
     VA pill in the page header. NOT --accent-cyan, which is a theme variable
     and would drift away from that pill on 9 of the 10 skins. */
  --va-accent: #06b6d4;

  /* Text */
  --text-primary: #e2e8f0;
  --text-secondary: #8892b0;
  --text-muted: #8294a7;
  --text-bright: #f8fafc;
  /* Foreground color to use ON top of the accent-blue background (filled buttons, badges).
     White for dark accents; themes with light accents (e.g. Ramp amber) override this. */
  --text-on-accent: #ffffff;
  /* Foreground on other semantic accents — default assumes bright/light orange bg (dark text). */
  --text-on-orange: #0b0d12;
  --text-on-green: #0b1a12;
  --text-on-cyan: #0b1018;

  /* Borders */
  --border-subtle: rgba(255,255,255,0.06);
  --border-medium: rgba(255,255,255,0.1);
  --border-primary: rgba(255,255,255,0.15);
  --border-accent: rgba(59,130,246,0.3);

  /* Shadows */
  --shadow-card: 0 4px 24px rgba(0,0,0,0.3);
  --shadow-elevated: 0 8px 32px rgba(0,0,0,0.4);
  /* One step below --shadow-card: lifts a small control off its rail (active tab pill). */
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.28);
  --shadow-glow-blue: 0 0 20px rgba(59,130,246,0.15);
  /* Bevel: inset highlight on top, inset shadow on bottom, + drop shadow → "carved metal" feel */
  --bevel-shadow:
    inset 0 1px 0 rgba(255,255,255,0.05),
    inset 0 -1px 0 rgba(0,0,0,0.28),
    0 1px 2px rgba(0,0,0,0.35);

  /* Layout */
  --sidebar-width: 220px;
  --sidebar-width-collapsed: 56px;
  --titlebar-height: 36px;

  /* Radius */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;

  /* Transitions */
  --transition-fast: 0.15s ease;
  --transition-normal: 0.25s ease;

  /* Typography — the mono stack this file already spells out literally ~40 times
     ('IBM Plex Mono', monospace). Loaded from Google Fonts in index.html. */
  --font-mono: 'IBM Plex Mono', monospace;

  /* ---- Compat aliases ----------------------------------------------------
     Names that grew organically in JSX inline styles and never made it into
     the palette above. An undefined custom property makes the WHOLE
     declaration invalid, so every one of these silently dropped its rule (or
     leaned on a hardcoded fallback that then ignored the active theme).
     Declaring them here as aliases costs nothing and needs no change in the
     13 theme blocks or the light-mode block: the substitution happens on
     <html>, the same element data-theme / data-mode sit on, so each alias
     resolves to the *current* theme's value — same trick as --accent-amber.
     Prefer the canonical name on the right in new code. */
  --bg-hover:           var(--bg-card-hover);
  --bg-base:            var(--bg-primary);
  --bg-page:            var(--bg-primary);
  --bg-titlebar:        var(--bg-primary);
  --bg-tertiary:        var(--bg-elevated);
  --bg-elev-2:          var(--bg-elevated);
  --card-bg:            var(--bg-card);
  --input-bg:           var(--bg-input);
  --border:             var(--border-medium);
  --border-color:       var(--border-medium);
  --border-subtle-soft: var(--border-subtle);
  --text:               var(--text-primary);
  --text-tertiary:      var(--text-muted);
  --text-faint:         var(--text-muted);
  --accent:             var(--accent-blue);
  --accent-primary:     var(--accent-blue);
  --warning:            var(--accent-orange);
}

/* ==================== THEME: OCEAN ==================== */
[data-theme="ocean"] {
  --bg-primary: #0a1a1e;
  --bg-secondary: #0f2027;
  --bg-card: #132a33;
  --bg-card-hover: #183842;
  --bg-elevated: #1e3d49;
  --bg-input: #091418;
  --accent-blue: #06b6d4;
  --accent-blue-dim: #0891b2;
  --border-accent: rgba(6,182,212,0.3);
  --shadow-glow-blue: 0 0 20px rgba(6,182,212,0.15);
}

/* ==================== THEME: FOREST ==================== */
[data-theme="forest"] {
  --bg-primary: #0a1a10;
  --bg-secondary: #0f2518;
  --bg-card: #13301e;
  --bg-card-hover: #183d28;
  --bg-elevated: #1e4a32;
  --bg-input: #081409;
  --accent-blue: #10b981;
  --accent-blue-dim: #059669;
  --border-accent: rgba(16,185,129,0.3);
  --shadow-glow-blue: 0 0 20px rgba(16,185,129,0.15);
}

/* ==================== THEME: SUNSET ==================== */
[data-theme="sunset"] {
  --bg-primary: #1a130a;
  --bg-secondary: #251a0f;
  --bg-card: #302013;
  --bg-card-hover: #3d2918;
  --bg-elevated: #4a321e;
  --bg-input: #140e08;
  --accent-blue: #f59e0b;
  --accent-blue-dim: #d97706;
  --border-accent: rgba(245,158,11,0.3);
  --shadow-glow-blue: 0 0 20px rgba(245,158,11,0.15);
}

/* ==================== THEME: AMETHYST ==================== */
[data-theme="amethyst"] {
  --bg-primary: #120a1a;
  --bg-secondary: #1a0f25;
  --bg-card: #221535;
  --bg-card-hover: #2c1d42;
  --bg-elevated: #362650;
  --bg-input: #0e0814;
  --accent-blue: #8b5cf6;
  --accent-blue-dim: #7c3aed;
  --border-accent: rgba(139,92,246,0.3);
  --shadow-glow-blue: 0 0 20px rgba(139,92,246,0.15);
}

/* ==================== THEME: NORD ==================== */
[data-theme="nord"] {
  --bg-primary: #2e3440;
  --bg-secondary: #3b4252;
  --bg-card: #434c5e;
  --bg-card-hover: #4c566a;
  --bg-elevated: #4c566a;
  --bg-input: #272c36;
  --accent-blue: #88c0d0;
  --accent-blue-dim: #5e81ac;
  --accent-cyan: #8fbcbb;
  --accent-green: #a3be8c;
  --accent-orange: #ebcb8b;
  --accent-red: #bf616a;
  --accent-purple: #b48ead;
  --text-primary: #eceff4;
  --text-secondary: #d8dee9;
  --text-muted: #9aa5b4;
  --text-bright: #eceff4;
  --border-subtle: rgba(216,222,233,0.08);
  --border-medium: rgba(216,222,233,0.12);
  --border-accent: rgba(136,192,208,0.3);
  --shadow-glow-blue: 0 0 20px rgba(136,192,208,0.15);
}

/* ==================== THEME: DRACULA ==================== */
[data-theme="dracula"] {
  --bg-primary: #282a36;
  --bg-secondary: #2d303e;
  --bg-card: #343746;
  --bg-card-hover: #3c3f52;
  --bg-elevated: #44475a;
  --bg-input: #21222c;
  --accent-blue: #bd93f9;
  --accent-blue-dim: #9b6dff;
  --accent-cyan: #8be9fd;
  --accent-green: #50fa7b;
  --accent-orange: #ffb86c;
  --accent-red: #ff5555;
  --accent-purple: #ff79c6;
  --text-primary: #f8f8f2;
  --text-secondary: #c0bfbc;
  --text-muted: #8899bb;
  --text-bright: #f8f8f2;
  --border-subtle: rgba(248,248,242,0.06);
  --border-medium: rgba(248,248,242,0.1);
  --border-accent: rgba(189,147,249,0.3);
  --shadow-glow-blue: 0 0 20px rgba(189,147,249,0.15);
}

/* ==================== THEME: SOLARIZED ==================== */
[data-theme="solarized"] {
  --bg-primary: #002b36;
  --bg-secondary: #073642;
  --bg-card: #0a3d49;
  --bg-card-hover: #0e4957;
  --bg-elevated: #134e5b;
  --bg-input: #00222b;
  --accent-blue: #268bd2;
  --accent-blue-dim: #1a6ba0;
  --accent-cyan: #2aa198;
  --accent-green: #859900;
  --accent-orange: #cb4b16;
  --accent-red: #dc322f;
  --accent-purple: #6c71c4;
  --accent-pink: #d33682;
  --text-primary: #93a1a1;
  --text-secondary: #839496;
  --text-muted: #8a9da6;
  --text-bright: #fdf6e3;
  --border-subtle: rgba(147,161,161,0.08);
  --border-medium: rgba(147,161,161,0.12);
  --border-accent: rgba(38,139,210,0.3);
  --shadow-glow-blue: 0 0 20px rgba(38,139,210,0.15);
}

/* ==================== THEME: MONOKAI ==================== */
[data-theme="monokai"] {
  --bg-primary: #272822;
  --bg-secondary: #2e2f28;
  --bg-card: #383930;
  --bg-card-hover: #42433a;
  --bg-elevated: #4b4c42;
  --bg-input: #1e1f1a;
  --accent-blue: #66d9ef;
  --accent-blue-dim: #3db8d0;
  --accent-cyan: #66d9ef;
  --accent-green: #a6e22e;
  --accent-orange: #fd971f;
  --accent-red: #f92672;
  --accent-purple: #ae81ff;
  --text-primary: #f8f8f2;
  --text-secondary: #c0c0b0;
  --text-muted: #9a9580;
  --text-bright: #f8f8f2;
  --border-subtle: rgba(248,248,242,0.06);
  --border-medium: rgba(248,248,242,0.1);
  --border-accent: rgba(102,217,239,0.3);
  --shadow-glow-blue: 0 0 20px rgba(102,217,239,0.15);
}

/* ==================== THEME: CATPPUCCIN ==================== */
[data-theme="catppuccin"] {
  --bg-primary: #1e1e2e;
  --bg-secondary: #24243b;
  --bg-card: #2a2a48;
  --bg-card-hover: #313155;
  --bg-elevated: #383862;
  --bg-input: #181826;
  --accent-blue: #89b4fa;
  --accent-blue-dim: #74a8f7;
  --accent-cyan: #94e2d5;
  --accent-green: #a6e3a1;
  --accent-orange: #fab387;
  --accent-red: #f38ba8;
  --accent-purple: #cba6f7;
  --accent-pink: #f5c2e7;
  --text-primary: #cdd6f4;
  --text-secondary: #bac2de;
  --text-muted: #8e95ab;
  --text-bright: #cdd6f4;
  --border-subtle: rgba(205,214,244,0.06);
  --border-medium: rgba(205,214,244,0.1);
  --border-accent: rgba(137,180,250,0.3);
  --shadow-glow-blue: 0 0 20px rgba(137,180,250,0.15);
}

/* ==================== THEME: SLATE ==================== */
[data-theme="slate"] {
  --bg-primary: #1e2630;
  --bg-secondary: #263040;
  --bg-card: #2e3a4a;
  --bg-card-hover: #364454;
  --bg-elevated: #3e4e5e;
  --bg-input: #182028;
  --accent-blue: #94a3b8;
  --accent-blue-dim: #64748b;
  --accent-cyan: #67e8f9;
  --accent-green: #86efac;
  --accent-orange: #fdba74;
  --accent-red: #fca5a5;
  --accent-purple: #c4b5fd;
  --text-primary: #e2e8f0;
  --text-secondary: #cbd5e1;
  --text-muted: #8294a7;
  --text-bright: #f1f5f9;
  --border-subtle: rgba(226,232,240,0.06);
  --border-medium: rgba(226,232,240,0.1);
  --border-accent: rgba(148,163,184,0.3);
  --shadow-glow-blue: 0 0 20px rgba(148,163,184,0.15);
}

/* ==================== THEME: GRUVBOX ==================== */
[data-theme="gruvbox"] {
  --bg-primary: #282828;
  --bg-secondary: #3c3836;
  --bg-card: #504945;
  --bg-card-hover: #5a524c;
  --bg-elevated: #665c54;
  --bg-input: #1d2021;
  --accent-blue: #83a598;
  --accent-blue-dim: #689d6a;
  --accent-cyan: #8ec07c;
  --accent-green: #b8bb26;
  --accent-orange: #fe8019;
  --accent-red: #fb4934;
  --accent-purple: #d3869b;
  --text-primary: #ebdbb2;
  --text-secondary: #d5c4a1;
  --text-muted: #a89b8c;
  --text-bright: #fbf1c7;
  --border-subtle: rgba(235,219,178,0.06);
  --border-medium: rgba(235,219,178,0.1);
  --border-accent: rgba(131,165,152,0.3);
  --shadow-glow-blue: 0 0 20px rgba(131,165,152,0.15);
}

/* ==================== THEME: RAMP (signature — navy + amber) ==================== */
[data-theme="ramp"] {
  --bg-primary: #0c131c;
  --bg-secondary: #111a27;
  --bg-card: #161f2d;
  --bg-card-hover: #1a2537;
  --bg-elevated: #1e2a3e;
  --bg-input: #0a1018;
  --accent-blue: #ffb347;
  --accent-blue-dim: #e69a2e;
  /* Amber is too light for white text — use dark navy as fg on accent bg. */
  --text-on-accent: #0c131c;
  --accent-cyan: #ffd580;
  --accent-green: #5ed97a;
  --accent-orange: #ff9f2e;
  --accent-red: #ff6b5b;
  --accent-purple: #c99af2;
  --accent-pink: #ff8fb4;
  --text-primary: #eaeef5;
  --text-secondary: #a3afc2;
  --text-muted: #8b97ab;
  --text-bright: #f5f8fc;
  --border-subtle: rgba(255,179,71,0.06);
  --border-medium: rgba(255,179,71,0.1);
  --border-primary: rgba(255,179,71,0.15);
  --border-accent: rgba(255,179,71,0.3);
  --shadow-glow-blue: 0 0 20px rgba(255,179,71,0.18);
}

/* ==================== MODE: LIGHT ==================== */
[data-mode="light"] {
  --bg-primary: #f0f2f5;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-card-hover: #f8f9fb;
  --bg-elevated: #f0f2f5;
  --bg-input: #f5f6f8;
  /* Same 6%, inked the other way: on paper a row darkens under the cursor. */
  --row-hover-overlay: rgba(0,0,0,0.06);
  --text-primary: #1a202c;
  --text-secondary: #4a5568;
  --text-muted: #5a6370;
  --text-bright: #0f1419;
  --border-subtle: rgba(0,0,0,0.08);
  --border-medium: rgba(0,0,0,0.12);
  --border-primary: rgba(0,0,0,0.15);
  --shadow-card: 0 2px 12px rgba(0,0,0,0.06);
  --shadow-elevated: 0 4px 20px rgba(0,0,0,0.08);
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
  /* Subtler bevel on paper — top highlight reads as light hitting the page edge */
  --bevel-shadow:
    inset 0 1px 0 rgba(255,255,255,0.6),
    inset 0 -1px 0 rgba(0,0,0,0.05),
    0 1px 1px rgba(0,0,0,0.06);
}

[data-mode="light"][data-theme="ocean"] {
  --bg-primary: #edf8fa;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #e6f4f7;
  --bg-input: #f0f9fa;
}

[data-mode="light"][data-theme="forest"] {
  --bg-primary: #edf8f2;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #e6f7ee;
  --bg-input: #f0faf4;
}

[data-mode="light"][data-theme="sunset"] {
  --bg-primary: #faf5ed;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #f7f0e6;
  --bg-input: #fdf9f2;
}

[data-mode="light"][data-theme="amethyst"] {
  --bg-primary: #f3edf8;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #ede6f7;
  --bg-input: #f7f2fa;
}

[data-mode="light"][data-theme="nord"] {
  --bg-primary: #eceff4;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #e5e9f0;
  --bg-input: #f0f3f7;
  --text-primary: #2e3440;
  --text-secondary: #4c566a;
  --text-muted: #5e6a7d;
  --text-bright: #2e3440;
}

[data-mode="light"][data-theme="dracula"] {
  --bg-primary: #f0ecf8;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #ebe6f5;
  --bg-input: #f5f2fa;
  --text-primary: #282a36;
  --text-secondary: #44475a;
  --text-muted: #505880;
  --text-bright: #282a36;
}

[data-mode="light"][data-theme="solarized"] {
  --bg-primary: #fdf6e3;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #eee8d5;
  --bg-input: #fdf9f0;
  --text-primary: #586e75;
  --text-secondary: #657b83;
  --text-muted: #586e75;
  --text-bright: #073642;
}

[data-mode="light"][data-theme="monokai"] {
  --bg-primary: #f5f5ef;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #eeeee8;
  --bg-input: #f9f9f4;
  --text-primary: #272822;
  --text-secondary: #49483e;
  --text-muted: #5c5845;
  --text-bright: #272822;
}

[data-mode="light"][data-theme="catppuccin"] {
  --bg-primary: #eff1f5;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #e6e9ef;
  --bg-input: #f2f4f8;
  --text-primary: #4c4f69;
  --text-secondary: #5c5f77;
  --text-muted: #606378;
  --text-bright: #4c4f69;
}

[data-mode="light"][data-theme="slate"] {
  --bg-primary: #f1f5f9;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #e2e8f0;
  --bg-input: #f8fafc;
  --text-primary: #1e293b;
  --text-secondary: #475569;
  --text-muted: #5a6577;
  --text-bright: #0f172a;
}

[data-mode="light"][data-theme="gruvbox"] {
  --bg-primary: #fbf1c7;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-elevated: #f2e5bc;
  --bg-input: #fdf6d8;
  --text-primary: #3c3836;
  --text-secondary: #504945;
  --text-muted: #6b6258;
  --text-bright: #282828;
}

/* Ramp LIGHT — Jeppesen chart-inspired: ivory paper, magenta + cyan, thin black */
[data-mode="light"][data-theme="ramp"] {
  /* Accent colors are all dark enough to need white text */
  --text-on-accent: #ffffff;
  --text-on-orange: #ffffff;
  --text-on-green: #ffffff;
  --text-on-cyan: #ffffff;
  --bg-primary: #faf6ed;
  --bg-secondary: #ffffff;
  --bg-card: #ffffff;
  --bg-card-hover: #f6f1e2;
  --bg-elevated: #f2ecdc;
  --bg-input: #fdfaf2;
  --accent-blue: #c2185b;
  --accent-blue-dim: #9e1049;
  --accent-cyan: #0097a7;
  --accent-green: #2e7d32;
  --accent-orange: #e65100;
  --accent-red: #c62828;
  --accent-purple: #6a1b9a;
  --accent-pink: #c2185b;
  --text-primary: #1a1a1a;
  --text-secondary: #3a3a3a;
  --text-muted: #6a6a6a;
  --text-bright: #000000;
  --border-subtle: rgba(0,0,0,0.08);
  --border-medium: rgba(0,0,0,0.14);
  --border-primary: rgba(0,0,0,0.2);
  --border-accent: rgba(194,24,91,0.3);
  --shadow-card: 0 2px 10px rgba(0,0,0,0.06);
  --shadow-elevated: 0 4px 20px rgba(0,0,0,0.08);
  --shadow-glow-blue: 0 0 12px rgba(194,24,91,0.1);
}

/* ==================== GAUGE (270° analog KPI, dark + light) ==================== */
/* Landscape card: SVG on left, text column on right. Height matches StatCard (~95-110px). */
.gauge-card {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 14px 16px 12px 10px;
  min-width: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  box-shadow: var(--bevel-shadow);
  transition: border-color 0.15s;
  /* Query container for the dial + value below: on a 5-up KPI grid the card
     bottoms out at its 170px floor, which left ~42px for a value needing ~60
     ("77.9%" spilled 18px past the card, incl. at the 1200px minimum window
     width). Everything sized in cqi shrinks with the card instead. */
  container-type: inline-size;
}
/* …but size containment also removes the card from its parent's intrinsic width.
   In a shrink-to-fit parent (inline-flex/inline-block/float) the card then
   collapses to its padding — measured at 26px on the Ratios cards, whose
   .ratio-value-box is inline-flex. Those two are wide enough that nothing
   overflowed there in the first place, so they opt out. Every other call site
   puts the Gauge straight into a grid, where the width is definite. */
.ratio-value-box > .gauge-card {
  container-type: normal;
}
.ratio-value-box > .gauge-card .gauge-svg { width: 90px; }
.ratio-value-box > .gauge-card .gauge-value { font-size: 22px; }
.ratio-value-box > .gauge-card .gauge-unit { font-size: 12px; }
/* Dial + text side by side; the optional trend row lives below (full card width). */
.gauge-main {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.gauge-clickable { cursor: pointer; user-select: none; }
.gauge-clickable:hover { border-color: var(--border-accent); }
.gauge-svg-wrap {
  flex-shrink: 0;
  display: inline-block;
  line-height: 0;
}
.gauge-svg {
  /* 90px whenever the card can afford it (content box ≳ 215px), shrinking to
     58px on the cramped 5-up grid so the value keeps a readable column. */
  width: clamp(58px, 42cqi, 90px);
  height: auto;
  flex-shrink: 0;
  display: block;
}
.gauge-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.gauge-value {
  font-family: 'IBM Plex Mono', monospace;
  /* Full 22px above a ~183px content box, down to 17px on the narrowest card. */
  font-size: clamp(17px, 12cqi, 22px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.3px;
  white-space: nowrap;
}
.gauge-unit { font-size: clamp(9px, 6.5cqi, 12px); margin-left: 2px; opacity: 0.7; }
.gauge-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.8px;
}
.gauge-detail {
  font-size: 11px;
  color: var(--text-secondary);
  line-height: 1.3;
}
.gauge-trend {
  line-height: 0;
  opacity: 0.85;
  min-width: 0;
  overflow: hidden;
}

/* ==================== STRIP GAUGE (linear KPI bar for tables) ==================== */
.strip-gauge {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 80px;
  vertical-align: middle;
}
/* When several StripGauges stack (e.g. per-engine condition bars), keep them vertically separated */
.strip-gauge + .strip-gauge { margin-top: 4px; }
.strip-gauge-track {
  position: relative;
  height: 7px;
  background: var(--bg-input);
  border: 1px solid var(--border-subtle);
  border-radius: 2px;
  overflow: hidden;
}
.strip-gauge-zone {
  position: absolute;
  top: 0; bottom: 0;
  opacity: 0.22;
  pointer-events: none;
}
.strip-gauge-fill {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  transition: width 0.3s ease;
  opacity: 0.85;
}
.strip-gauge-tick {
  position: absolute;
  top: 0; bottom: 0;
  width: 1px;
  background: var(--border-medium);
  opacity: 0.6;
  pointer-events: none;
}
.strip-gauge-indicator {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 2px;
  transform: translateX(-1px);
  box-shadow: 0 0 4px var(--bg-card);
  transition: left 0.3s ease;
}
.strip-gauge-label {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
  text-align: center;
  line-height: 1;
}
[data-mode="light"] .strip-gauge-track { background: rgba(0,0,0,0.06); }
[data-mode="light"] .strip-gauge-zone { opacity: 0.35; }

/* ==================== DISTRIBUTION RING (segmented donut + legend) ==================== */
.dist-ring-card {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  min-width: 0;
  box-shadow: var(--bevel-shadow);
}
.dist-ring-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--text-muted);
  margin-bottom: 8px;
}
.dist-ring-body {
  display: flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
}
.dist-ring-svg {
  flex-shrink: 0;
  display: block;
}
.dist-ring-legend {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
}
.dist-ring-legend-item {
  display: grid;
  grid-template-columns: 10px 1fr auto auto;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.dist-ring-legend-dot {
  width: 9px; height: 9px; border-radius: 2px;
}
.dist-ring-legend-label {
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dist-ring-legend-value {
  font-family: 'IBM Plex Mono', monospace;
  font-weight: 700;
  color: var(--text-primary);
  text-align: right;
}
.dist-ring-legend-pct {
  font-family: 'IBM Plex Mono', monospace;
  color: var(--text-muted);
  font-size: 10px;
  min-width: 32px;
  text-align: right;
}

/* ==================== STACKED BAR (compact distribution for tables) ==================== */
.stacked-bar {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.stacked-bar-inline {
  flex-direction: row;
  align-items: center;
  gap: 10px;
}
.stacked-bar-track {
  display: flex;
  width: 100%;
  background: var(--bg-input);
  border: 1px solid var(--border-subtle);
  border-radius: 2px;
  overflow: hidden;
}
.stacked-bar-seg {
  display: block;
  height: 100%;
  opacity: 0.9;
  border-right: 1px solid var(--bg-card);
}
.stacked-bar-seg:last-child { border-right: none; }
.stacked-bar-empty { flex: 1; opacity: 0.3; }
.stacked-bar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  font-size: 10px;
}
.stacked-bar-inline .stacked-bar-legend { flex: 1; }
.stacked-bar-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}
.stacked-bar-legend-dot {
  width: 8px; height: 8px; border-radius: 1px;
}
.stacked-bar-legend-label { color: var(--text-secondary); }
.stacked-bar-legend-value {
  font-family: 'IBM Plex Mono', monospace;
  font-weight: 700;
  color: var(--text-primary);
}
.stacked-bar-legend-pct {
  font-family: 'IBM Plex Mono', monospace;
  color: var(--text-muted);
}
.stacked-bar-legend-total {
  font-family: 'IBM Plex Mono', monospace;
  color: var(--text-secondary);
  font-weight: 600;
  margin-left: auto;
}

[data-mode="light"] .stacked-bar-track { background: rgba(0,0,0,0.06); }
[data-mode="light"] .stacked-bar-seg { border-right-color: #fff; }

/* ==================== FLIGHT STRIP (ATC paper strip layout) ==================== */
.fstrip {
  --fstrip-accent: var(--accent-blue);
  position: relative;
  display: flex;
  align-items: stretch;
  background: var(--bg-elevated);
  border: 1px solid var(--border-subtle);
  box-shadow: var(--bevel-shadow);
  border-radius: 3px;
  overflow: hidden;
  min-height: 54px;
  margin-bottom: 6px;
  /* Clip the top-right corner for the classic strip "dog-ear" look */
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 0 100%);
  transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}
.fstrip-clickable { cursor: pointer; user-select: none; }
.fstrip-clickable:hover {
  border-color: var(--fstrip-accent);
  box-shadow: 0 2px 10px rgba(0,0,0,0.35);
}
/* Hover tint on EVERY strip, clickable or not — following the eye across a
   wide row is not a click affordance (ticket #12030). Until now a strip only
   answered the cursor when it had an onClick, and then only through its
   border: the halo above is an OUTSET shadow, and `.fstrip`'s clip-path (the
   dog-ear corner) clips it away, so it has never been drawn (noted in FR-010).
   An extra background LAYER is drawn inside the clip region and leaves the
   strip's own background — light mode's paper gradient included — untouched.
   Declared BEFORE `.lc-job-row-selected` on purpose: that rule sets the
   `background` shorthand at equal specificity, so a selected row keeps its
   blue and never wears both states at once. */
.fstrip:hover {
  background-image: linear-gradient(var(--row-hover-overlay), var(--row-hover-overlay));
}
.fstrip-highlight {
  animation: fstripFlash 1.4s ease-out;
}
@keyframes fstripFlash {
  0%   { box-shadow: 0 0 0 2px var(--fstrip-accent); }
  100% { box-shadow: 0 0 0 0 transparent; }
}
/* Persistent "selected job" ring in the split-view list pane (strips). The
   fstrip-highlight flash plays once; this keeps the selection visible so it
   stays in sync with the highlighted route on the map (list <-> map sync).

   ⚠️ The ring MUST be `inset`. `.fstrip` carries a clip-path (the dog-ear
   corner), and a clip-path clips the element's whole rendering — including any
   OUTSET box-shadow. The previous `0 0 0 2px` ring was therefore never drawn,
   which left a 16% tint as the only difference between a selected row and its
   neighbours ("accordions/expanders are shaded very similarly", tester report
   2026-08-12). Inset is painted inside the clip region and survives.
   The border-color and the deeper tint carry the state on the LUMINANCE
   channel too, so it does not rest on hue alone. */
.fstrip.lc-job-row-selected,
.fstrip.fc-job-row-selected {
  box-shadow: inset 0 0 0 2px var(--accent-blue);
  /* Themed, not rgba(59,130,246,…): --accent-blue is #ffb347 on at least one
     shipped theme, where a hardcoded blue tint under an amber ring painted the
     same state in two colours at once. Same defect family as the #8b5cf6 the
     lot-7 guard pins. */
  background: color-mix(in srgb, var(--accent-blue) 20%, transparent);
  border-color: var(--accent-blue);
  position: relative;
  z-index: 1;
}
/* Pulsing ring on the dep/dest airports of the job selected in the split-view
   list pane. Anchored at the marker's latlng (iconSize 0 → top-left = point),
   the negative margins center the 34px ring on it. */
.lc-airport-halo {
  width: 34px;
  height: 34px;
  margin-left: -17px;
  margin-top: -17px;
  border-radius: 50%;
  border: 3px solid #22d3ee;
  box-shadow: 0 0 12px 2px rgba(34, 211, 238, 0.85);
  animation: lc-airport-halo-pulse 1.3s ease-in-out infinite;
  pointer-events: none;
}
@keyframes lc-airport-halo-pulse {
  0%, 100% { transform: scale(0.8);  opacity: 0.55; }
  50%      { transform: scale(1.25); opacity: 1; }
}

.fstrip-accent {
  flex: 0 0 22px;
  background: var(--fstrip-accent);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: inherit;
}
.fstrip-accent-label {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1px;
  color: var(--text-on-accent, #fff);
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
  text-transform: uppercase;
  user-select: none;
  cursor: inherit;
}
/* Bottom-right short tag for the source family (CHR / TUR / LOG / FBO / RTE / VA).
   Rendered inside the source half of the diagonal split. Always white-on-dark
   because every SOURCE_COLOR in the v2 palette is intentionally dark. */
.fstrip-accent-label-bot {
  position: absolute;
  right: 1px;
  bottom: 2px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: rgba(255,255,255,0.92);
  text-transform: uppercase;
  user-select: none;
  pointer-events: none;
  line-height: 1;
}
/* Top-left short tag for the goods type (GDS / PER / FRG / DGR / MED / PAX...).
   The counterpart of .fstrip-accent-label-bot: the dark half named its source
   family in letters, the bright half named the cargo type by HUE ALONE — which
   is nothing at all for a colourblind player (tester report, 2026-08-12).
   Unlike the bottom tag, the color is NOT fixed: this half is bright on every
   goods hue but one (UNDERCOVER), so callers pass getGoodsTagFg(accent). */
.fstrip-accent-label-top {
  position: absolute;
  left: 2px;
  top: 2px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: #0b1220;
  text-transform: uppercase;
  user-select: none;
  pointer-events: none;
  line-height: 1;
}
.fstrip-accent { position: relative; }
.fstrip-nested .fstrip-accent-label-bot { font-size: 7px; right: 1px; bottom: 1px; }
.fstrip-nested .fstrip-accent-label-top { font-size: 7px; left: 1px; top: 1px; }
/* Right-edge rail — "this row belongs to your Virtual Airline".
   The LEFT edge is already spoken for (goods type x source family diagonal)
   and so is the blue ring (.lc-job-row-selected = selected row), which leaves
   the right edge as the only free channel on a strip.
   Absolutely positioned ON PURPOSE: a flex sibling would consume 4px of real
   row width and drift the sticky sort-chip header away from the cells it
   labels — the exact class of bug services/job-list-columns.js exists to stop.
   The dog-ear clip-path shaves its top corner, which is the intended
   silhouette (a full border would instead look severed there).
   pointer-events stay ON so a click on the rail still bubbles to the row. */
.fstrip-rail-va {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 4px;
  background: var(--va-accent);
  cursor: inherit;
}
.fstrip-body {
  display: flex;
  flex: 1;
  min-width: 0;
  align-items: stretch;
}
.fstrip-cell {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 6px 10px;
  border-right: 1px solid var(--border-subtle);
  min-width: 0;
}
.fstrip-cell:last-child { border-right: none; }
.fstrip-cell-label {
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 8px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-muted);
  margin-bottom: 2px;
  line-height: 1;
  /* Clip to the cell. A flex cell squeezed to 0 on a narrow window still
     rendered its label at full width, which then bled over the neighbour's —
     Employees showed "STATNOUTES" where STATUS overlapped ROUTES. Wrapping is
     deliberately left alone (no nowrap): multi-word labels like "NEXT
     MAINTENANCE" are meant to break across two lines. */
  overflow: hidden;
}
.fstrip-cell-value {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Opt-in via <FlightStripCell nowrap> — for cells holding a single figure
   (money, weight, distance, counts). Without it a value that outgrows its
   column reflows onto a second line instead of clipping, which is how the
   Company Jobs pay amount ended up sitting lower on some rows than others. */
.fstrip-cell-nowrap { white-space: nowrap; }

/* Nested child strip (legs under a mission) — smaller, indented, no dog-ear */
.fstrip-nested {
  min-height: 38px;
  margin-left: 22px;
  margin-top: -4px;
  margin-bottom: 4px;
  clip-path: none;
  border-left: none;
  border-top: 1px dashed var(--border-subtle);
  background: var(--bg-card);
}
.fstrip-nested .fstrip-accent { flex: 0 0 14px; }
.fstrip-nested .fstrip-accent-label { font-size: 8px; letter-spacing: 0.8px; }
.fstrip-nested .fstrip-cell { padding: 4px 8px; }
.fstrip-nested .fstrip-cell-label { font-size: 8px; margin-bottom: 1px; }
.fstrip-nested .fstrip-cell-value { font-size: 12px; font-weight: 500; }
/* Delivered legs: row reads as greyed, but the status cell (which carries
   the "Delivered" badge) stays at full opacity so the green status text
   remains clearly visible above the muted row. Opacity is applied per-child
   instead of on the parent because CSS opacity stacks multiplicatively —
   a child can't be made brighter than its parent.
   Same logic mirrored in the list/table view via `tr.tr-leg-delivered`. */
.fstrip-nested.fstrip-delivered > .fstrip-accent,
.fstrip-nested.fstrip-delivered .fstrip-cell:not(.fstrip-cell-status) { opacity: 0.5; }
tr.tr-leg-delivered > td:not(.td-leg-status) { opacity: 0.5; }
[data-mode="light"] .fstrip-nested { background: #f5ede0; }
[data-mode="light"] .fstrip-nested.fstrip-delivered { background: rgba(0,0,0,0.03); }

/* 3rd-level row — leg under a tour sub-mission. Without this extra indent,
   the leg row sits at the same `.fstrip-nested` margin-left as its parent
   sub-mission row and the hierarchy becomes unreadable. */
.fstrip-nested.fstrip-nested-leg { margin-left: 44px; }

/* Caret indicator on strip/table rows (rotates when expanded).
   2026-07-11 UX pass (ticket #10161 follow-up): promoted from a bare 10px
   muted triangle — which testers simply couldn't find — to an explicit
   cyan pill button. Shared by all expandable lists (fleet, flight-logs,
   pending-jobs, ongoing-flights, routes, logistic-center,
   freelance-charters, flight-score-panel): restyle here, not per-page.
   Do NOT re-add per-call-site font-size/width/height inline styles — they
   override this block and shrink the pill back to invisibility. */
.fstrip-caret {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  font-size: 10px;
  color: var(--accent-cyan);
  background: color-mix(in srgb, var(--accent-cyan) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-cyan) 45%, transparent);
  border-radius: 5px;
  cursor: pointer;
  transition: transform 0.15s ease, background 0.12s ease, border-color 0.12s ease;
}
.fstrip-caret.expanded { transform: rotate(90deg); }
/* Row-level affordance: hovering ANYWHERE on an expandable row (paper strip
   or table row) lights the caret up, so the whole row reads as "this opens"
   (strips with onClick already get .fstrip-clickable cursor + accent border). */
.fstrip-clickable:hover .fstrip-caret,
tr:hover .fstrip-caret {
  background: color-mix(in srgb, var(--accent-cyan) 24%, transparent);
  border-color: var(--accent-cyan);
}
/* Direct hover on the caret itself — strongest state. */
.fstrip-caret:hover {
  background: color-mix(in srgb, var(--accent-cyan) 35%, transparent);
  border-color: var(--accent-cyan);
}

/* Subtle paper grain via diagonal gradient in light mode (makes the strip feel like stock paper) */
[data-mode="light"] .fstrip {
  background:
    linear-gradient(135deg, transparent 0, rgba(0,0,0,0.012) 50%, transparent 100%),
    #faf5e8;
  border-color: rgba(0,0,0,0.15);
  box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Two layers, tint above grain: `background-image` is one property, so the
   plain `.fstrip:hover` rule would drop the paper gradient for as long as the
   cursor sits on the row. */
[data-mode="light"] .fstrip:hover {
  background-image:
    linear-gradient(var(--row-hover-overlay), var(--row-hover-overlay)),
    linear-gradient(135deg, transparent 0, rgba(0,0,0,0.012) 50%, transparent 100%);
}
[data-mode="light"] .fstrip-cell { border-right-color: rgba(0,0,0,0.08); }
[data-mode="light"] .fstrip-clickable:hover {
  box-shadow: 0 2px 8px rgba(0,0,0,0.12);
}

/* ==================== VIEW TOGGLE (table <=> strips) ==================== */
.view-toggle {
  display: inline-flex;
  gap: 0;
  border: 1px solid var(--border-subtle);
  border-radius: 4px;
  overflow: hidden;
  /* Opaque for the same reason as `.main-tab-btn` (#10646): `.ambient-map` is
     painted app-wide behind a `.main-content` that carries no background, so a
     see-through control lets the decorative map's coastlines run under its own
     icons. Painted on the CONTAINER rather than on `.view-toggle-btn`, which
     covers the gutters between the two buttons in one declaration.
     ⚠️ The value is `--bg-card` and NOT `--bg-primary` — the difference matters
     here and did not for the tabs. This control lives in TWO contexts: inside
     `.table-container` (`--bg-card`) on the table views, and on a bare toolbar
     over the page ground on the strip views (fleet.jsx:1671). No single value
     is invisible in both, so the choice is made on which way the error shows:
     `--bg-card` is invisible in the first and a barely-lighter relief in the
     second — which reads as a segmented control — whereas `--bg-primary` would
     stamp a dark rectangle onto a lighter card. */
  background: var(--bg-card);
}
.view-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0;
  transition: background 0.12s, color 0.12s;
}
.view-toggle-btn svg {
  width: 14px;
  height: 14px;
}
.view-toggle-btn:hover {
  background: var(--bg-card-hover);
  color: var(--text-secondary);
}
.view-toggle-btn.active {
  background: var(--accent-blue);
  color: var(--text-on-accent);
}
.view-toggle-btn.active:hover {
  background: var(--accent-blue-dim);
  color: var(--text-on-accent);
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  height: 100%;
  background: var(--bg-primary);
}
html, body {
  overflow: hidden;
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  color: var(--text-primary);
  -webkit-font-smoothing: antialiased;
}
body {
  height: 100%;
  background: transparent;  /* let .ambient-map show through at z-index:-1 */
}

/* ==================== FONT FAMILY (decoupled from theme) ====================
   User picks between IBM Plex Sans (default) and Barlow Condensed
   (Jeppesen-chart style). Selected via [data-font="..."] on <html>.
   Monospace stays IBM Plex Mono (tabular data, gauge readouts, etc.) in
   all cases — those elements hard-code 'IBM Plex Mono'. */
[data-font="jeppesen"] {
  font-family: 'Barlow Condensed', 'IBM Plex Sans', -apple-system, sans-serif;
  letter-spacing: 0.2px;
}
[data-font="jeppesen"] body,
[data-font="jeppesen"] .titlebar,
[data-font="jeppesen"] .sidebar,
[data-font="jeppesen"] h1,
[data-font="jeppesen"] h2,
[data-font="jeppesen"] h3,
[data-font="jeppesen"] h4,
[data-font="jeppesen"] button,
[data-font="jeppesen"] input,
[data-font="jeppesen"] select,
[data-font="jeppesen"] textarea,
[data-font="jeppesen"] label {
  font-family: 'Barlow Condensed', 'IBM Plex Sans', -apple-system, sans-serif;
}
/* Keep mono font-family on elements explicitly asking for it (tabular data,
   gauge values, flight strips, etc.). Their CSS already hard-codes
   'IBM Plex Mono', so no override needed here. */

/* ==================== PINNED FLIGHT STRIP (top, when in flight) ==================== */
.pinned-flight-strip {
  position: fixed;
  top: var(--titlebar-height);
  left: var(--sidebar-width);
  right: 0;
  z-index: 9998;
  padding: 4px 8px 0;
  background: linear-gradient(to bottom, var(--bg-primary) 0%, var(--bg-primary) 80%, transparent 100%);
  pointer-events: none;
}
.pinned-flight-strip > .fstrip {
  margin-bottom: 0;
  pointer-events: auto;
}
/* The strip is fixed over the top of the content area. Until 2026-09-12 nothing
   moved the page down, so the strip covered the page header — Back/Forward,
   the page title, Buy Aircraft / Export CSV / Refresh on Fleet (measured with
   elementFromPoint). Push the scrolling content below it. 58px = the strip's
   4px top padding + its 54px row, measured in the running app. */
:root { --pinned-strip-height: 58px; }
body:has(.pinned-flight-strip) .main-content {
  top: calc(var(--titlebar-height) + var(--pinned-strip-height));
}
body.staff-impersonating:has(.pinned-flight-strip) .main-content {
  top: calc(var(--titlebar-height) + var(--staff-banner-height) + var(--pinned-strip-height));
}
/* Web mode has no Electron titlebar, so the strip sits at the very top */
.web-mode .pinned-flight-strip { top: 0; }
/* On small viewports (mobile / narrow web), sidebar is hidden — strip spans full width */
@media (max-width: 800px) {
  .pinned-flight-strip { left: 0; }
}

/* ==================== DOUBLE BORDERS + TICK MARKS (Ramp light panels) ====================
   Signature Jeppesen chart frame: a second hairline inside the main border
   via outline with negative offset. Tick marks at the 4 corners via a
   stacked ::after pseudo-element — it sits on top of the content but is
   pointer-events:none so nothing interactive is affected. */
[data-mode="light"][data-theme="ramp"] .panel,
[data-mode="light"][data-theme="ramp"] .card,
[data-mode="light"][data-theme="ramp"] .stat-card,
[data-mode="light"][data-theme="ramp"] .gauge-card,
[data-mode="light"][data-theme="ramp"] .dist-ring-card,
[data-mode="light"][data-theme="ramp"] .ratio-card,
[data-mode="light"][data-theme="ramp"] .modal-panel,
[data-mode="light"][data-theme="ramp"] .fstrip {
  outline: 1px solid rgba(0,0,0,0.18);
  outline-offset: -4px;
}

/* Corner tick marks — small 7px L-shape at each of the 4 corners.
   Drawn via four box-shadows on a single ::after element. */
[data-mode="light"][data-theme="ramp"] .panel,
[data-mode="light"][data-theme="ramp"] .card,
[data-mode="light"][data-theme="ramp"] .gauge-card,
[data-mode="light"][data-theme="ramp"] .dist-ring-card,
[data-mode="light"][data-theme="ramp"] .ratio-card,
[data-mode="light"][data-theme="ramp"] .modal-panel {
  position: relative;
}
[data-mode="light"][data-theme="ramp"] .panel > .jepp-tick-overlay,
[data-mode="light"][data-theme="ramp"] .card > .jepp-tick-overlay,
[data-mode="light"][data-theme="ramp"] .gauge-card > .jepp-tick-overlay,
[data-mode="light"][data-theme="ramp"] .modal-panel > .jepp-tick-overlay {
  display: block;
}
/* For a lightweight approach without touching every JSX file, use a single
   ::after pseudo-element to paint 4 corner ticks via a background image.
   An SVG data-URI with 4 tiny L shapes works well and auto-scales. */
[data-mode="light"][data-theme="ramp"] .panel::after,
[data-mode="light"][data-theme="ramp"] .card::after,
[data-mode="light"][data-theme="ramp"] .gauge-card::after,
[data-mode="light"][data-theme="ramp"] .dist-ring-card::after,
[data-mode="light"][data-theme="ramp"] .modal-panel::after,
[data-mode="light"][data-theme="ramp"] .ratio-card::after {
  content: '';
  position: absolute;
  inset: 2px;
  pointer-events: none;
  background:
    linear-gradient(to right,  rgba(0,0,0,0.45), rgba(0,0,0,0.45)) top left    / 6px 1px no-repeat,
    linear-gradient(to bottom, rgba(0,0,0,0.45), rgba(0,0,0,0.45)) top left    / 1px 6px no-repeat,
    linear-gradient(to right,  rgba(0,0,0,0.45), rgba(0,0,0,0.45)) top right   / 6px 1px no-repeat,
    linear-gradient(to bottom, rgba(0,0,0,0.45), rgba(0,0,0,0.45)) top right   / 1px 6px no-repeat,
    linear-gradient(to right,  rgba(0,0,0,0.45), rgba(0,0,0,0.45)) bottom left / 6px 1px no-repeat,
    linear-gradient(to bottom, rgba(0,0,0,0.45), rgba(0,0,0,0.45)) bottom left / 1px 6px no-repeat,
    linear-gradient(to right,  rgba(0,0,0,0.45), rgba(0,0,0,0.45)) bottom right/ 6px 1px no-repeat,
    linear-gradient(to bottom, rgba(0,0,0,0.45), rgba(0,0,0,0.45)) bottom right/ 1px 6px no-repeat;
}

/* .stat-card already uses ::before (accent stripe) and ::after (ambient — wait no, not ::after).
   Reuse ::after for ticks too. */
[data-mode="light"][data-theme="ramp"] .stat-card::after {
  content: '';
  position: absolute;
  inset: 2px;
  pointer-events: none;
  background:
    linear-gradient(to right,  rgba(0,0,0,0.45), rgba(0,0,0,0.45)) top left    / 6px 1px no-repeat,
    linear-gradient(to bottom, rgba(0,0,0,0.45), rgba(0,0,0,0.45)) top left    / 1px 6px no-repeat,
    linear-gradient(to right,  rgba(0,0,0,0.45), rgba(0,0,0,0.45)) top right   / 6px 1px no-repeat,
    linear-gradient(to bottom, rgba(0,0,0,0.45), rgba(0,0,0,0.45)) top right   / 1px 6px no-repeat,
    linear-gradient(to right,  rgba(0,0,0,0.45), rgba(0,0,0,0.45)) bottom left / 6px 1px no-repeat,
    linear-gradient(to bottom, rgba(0,0,0,0.45), rgba(0,0,0,0.45)) bottom left / 1px 6px no-repeat,
    linear-gradient(to right,  rgba(0,0,0,0.45), rgba(0,0,0,0.45)) bottom right/ 6px 1px no-repeat,
    linear-gradient(to bottom, rgba(0,0,0,0.45), rgba(0,0,0,0.45)) bottom right/ 1px 6px no-repeat;
}

/* ==================== AMBIENT MAP (decorative dimmed Leaflet bg) ====================
   Uses `dark_all` tiles in both modes — dark base + thin light roads give
   a strong geographic silhouette. In light mode the dark pixels tint the
   ivory bg to create a subtle watermark. */
.ambient-map {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.18;
  filter: grayscale(100%) contrast(1.1);
  transition: opacity 0.35s ease;
  background: transparent;
}
.ambient-map-hidden { opacity: 0; }
/* Light mode: blend dark tiles into ivory as a pencil-sketch watermark */
[data-mode="light"] .ambient-map {
  opacity: 0.55;
  filter: grayscale(100%) invert(1) contrast(1.35) brightness(0.85);
  mix-blend-mode: multiply;
}
/* Leaflet's default white bg would ruin the fade. Keep the container bg-free. */
.ambient-map .leaflet-container { background: transparent; }

/* ==================== REUSABLE PANEL UTILITIES ====================
   .panel        — generic card-shaped panel with the full design system
                   (bg-card + subtle border + bevel). Use instead of
                   inline `background: var(--bg-card); border: ...` styles.
   .panel-accent — adds a 3px bottom stripe colored by --accent-color.
                   Usage: <div class="panel panel-accent" style="--accent-color: var(--accent-green)">...</div>
   .modal-panel  — for popups/dialogs (bigger shadow, larger radius).
   All three co-exist with the existing .card / .stat-card classes. */
.panel {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--bevel-shadow);
  position: relative;
}
.panel-accent {
  overflow: hidden;
}
.panel-accent::after {
  content: '';
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 3px;
  background: var(--accent-color, var(--accent-blue));
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  opacity: 0.8;
  pointer-events: none;
}
.modal-panel {
  background: var(--bg-elevated);
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-lg);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.05),
    inset 0 -1px 0 rgba(0,0,0,0.28),
    0 12px 40px rgba(0,0,0,0.55);
  position: relative;
}
[data-mode="light"] .modal-panel {
  background: #ffffff;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.8),
    inset 0 -1px 0 rgba(0,0,0,0.06),
    0 10px 30px rgba(0,0,0,0.18);
}

/* ==================== TEXTURES & DEPTH ====================
   - grain: fixed SVG noise overlay over the whole viewport (dark mode only)
   - bevel: inset highlight + drop shadow on cards (gives a carved metal feel)
   - CRT: thin scanlines on data-sensitive zones (applied via .crt class) */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9997;
  opacity: 0.025;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='3' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.7 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  background-size: 160px 160px;
  mix-blend-mode: overlay;
}
[data-mode="light"] body::after {
  /* paper grain: warmer tone, different blend so it shows on ivory without looking dirty */
  opacity: 0.04;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='3' seed='7' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.3  0 0 0 0 0.25  0 0 0 0 0.2  0 0 0 0.6 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
}

/* CRT scanline overlay — apply via class .crt on any container with data readouts.
   Uses ::after so it doesn't interfere with the element's own content. */
.crt { position: relative; }
.crt::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent 2px,
    rgba(0,0,0,0.18) 2px,
    rgba(0,0,0,0.18) 3px
  );
  mix-blend-mode: multiply;
  opacity: 0.55;
}
[data-mode="light"] .crt::after {
  /* Scanlines don't belong on paper-chart surfaces — soften a lot */
  opacity: 0.15;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent 3px,
    rgba(0,0,0,0.06) 3px,
    rgba(0,0,0,0.06) 4px
  );
}

/* ==================== TITLEBAR ==================== */
.titlebar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--titlebar-height);
  background: var(--bg-primary);
  border-bottom: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 150px 0 12px;
  -webkit-app-region: drag;
  z-index: 1000;
  user-select: none;
}

/* macOS — when windowed (not fullscreen), the OS traffic-light buttons sit
   in the top-left corner. Push our titlebar content right so the logo /
   "OnAir Companion" text doesn't get covered by the close/min/max buttons. */
body.mac-window-buttons .titlebar {
  padding-left: 82px;
}

/* ==================== STAFF "LOG AS" BANNER ==================== */
/* Fixed strip just below the titlebar while a staff impersonation is active.
   Triggers `body.staff-impersonating` which pushes sidebar/main-content down. */
:root { --staff-banner-height: 32px; }
.staff-banner {
  position: fixed;
  top: var(--titlebar-height);
  left: 0; right: 0;
  height: var(--staff-banner-height);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: var(--accent-purple, #a855f7);
  color: #000;
  font-size: 12px;
  font-weight: 700;
  font-family: 'IBM Plex Sans', sans-serif;
  box-shadow: 0 2px 8px rgba(168,85,247,0.35);
  z-index: 999;
}
.staff-banner strong { color: #000; }
.staff-banner-revert {
  padding: 3px 12px;
  font-size: 11px;
  font-weight: 700;
  background: #fff;
  color: #000;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  letter-spacing: 0.3px;
}
.staff-banner-revert:hover { background: #f3f4f6; }
body.staff-impersonating .pinned-flight-strip,
body.staff-impersonating .sidebar,
body.staff-impersonating .main-content {
  top: calc(var(--titlebar-height) + var(--staff-banner-height));
}
body.staff-impersonating .loading-overlay.scoped {
  top: calc(var(--titlebar-height) + var(--staff-banner-height) + 48px);
}
body.staff-impersonating:has(.pinned-flight-strip) .loading-overlay.scoped {
  top: calc(var(--titlebar-height) + var(--staff-banner-height) + 48px + 56px);
}

/* ==================== SCHEDULED-MAINTENANCE COUNTDOWN PILL ==================== */
/* Floating pill (top-center, just below the titlebar) counting down mm:ss
   during the last hour before a scheduled server maintenance. Rendered by
   components/maintenance-countdown-banner.jsx. The body-state variants keep
   it clear of the staff "Log As" banner and the pinned flight strip. */
.maintenance-countdown-pill {
  position: fixed;
  top: calc(var(--titlebar-height) + 8px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 9600;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 700;
  background: rgba(245, 158, 11, 0.12);
  border: 1px solid rgba(245, 158, 11, 0.45);
  color: var(--accent-amber, #f59e0b);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(6px);
  white-space: nowrap;
  /* ⚠️ Ne JAMAIS avaler un clic. Cette pastille est purement informative, mais
     elle flotte au-dessus de la rangée de navigation rapide de l'entête.
     MESURÉ dans l'app le 2026-09-01, viewport 1920, zoom d'interface 100 % :
     avec son libellé réel (« Scheduled server maintenance in » + chronomètre)
     elle fait 267 px de large et rend QUATRE boutons incliquables — Dashboard,
     Freelance Job, Pending Jobs, Fleet — un cinquième étant effleuré. Au centre
     même de la pastille, le clic atterrissait sur elle.

     Et le moment est le pire possible : elle s'affiche pendant la dernière
     heure avant une maintenance programmée, c'est-à-dire précisément quand le
     joueur a besoin de naviguer pour finir son vol et l'enregistrer.

     Le recouvrement n'est pas un cas limite : la pastille est centrée sur le
     viewport et la rangée d'icônes est ancrée à droite, donc la marge se ferme
     de MOITIÉ à chaque pixel de fenêtre perdu — alors que main.js autorise une
     fenêtre de 1200 px et que le réglage « Interface Zoom » rétrécit encore le
     viewport effectif.

     `pointer-events: none` en fait une propriété plutôt qu'une marge à
     surveiller : à n'importe quelle largeur et n'importe quel zoom, le clic
     traverse. Coût assumé : l'infobulle au survol ne se déclenche plus ; le
     `title` reste lisible par les technologies d'assistance, et le libellé
     porte l'essentiel. C'est la leçon du balayage de zones mortes de la carte. */
  pointer-events: none;
}
/* Redémarrage de la simulation du monde — même ancrage que la pastille de
   maintenance ci-dessus. Les deux sont montées côte à côte dans AppBanners,
   donc le sélecteur de frère plus bas les empile si elles s'affichent
   ensemble (une maintenance programmée et un redémarrage de simulation sont
   des événements indépendants : rien n'interdit qu'ils se chevauchent). */
.world-restarting-pill {
  position: fixed;
  top: calc(var(--titlebar-height) + 8px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 9600;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 700;
  background: rgba(56, 189, 248, 0.12);
  border: 1px solid rgba(56, 189, 248, 0.45);
  color: var(--accent-cyan, #38bdf8);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(6px);
  white-space: nowrap;
  /* ⚠️ Ne JAMAIS avaler un clic. Mesuré le 2026-09-01 : la pastille est centrée
     sur le viewport et la rangée d'icônes de l'entête est ancrée à droite, donc
     la marge entre les deux (24 px à 1920) se ferme de moitié à chaque pixel de
     fenêtre perdu et s'annule vers ~1872 px — alors que main.js autorise une
     fenêtre de 1200, et que le zoom d'interface rétrécit encore le viewport
     effectif. Autrement dit la collision est le cas NORMAL, pas le cas limite.
     `pointer-events: none` en fait une propriété au lieu d'une marge à
     surveiller : à n'importe quelle largeur et n'importe quel zoom, le clic
     traverse. Coût assumé : l'infobulle au survol ne se déclenche plus (le
     `title` reste pour les technologies d'assistance, et le libellé porte
     l'essentiel). C'est la leçon du balayage de zones mortes de la carte. */
  pointer-events: none;
}
.maintenance-countdown-pill ~ .world-restarting-pill {
  top: calc(var(--titlebar-height) + 46px);
}
.maintenance-countdown-pill .mcp-time {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.5px;
}
.maintenance-countdown-pill.critical {
  background: rgba(239, 68, 68, 0.14);
  border-color: rgba(239, 68, 68, 0.55);
  color: var(--accent-red, #ef4444);
  animation: mcpPulse 1s ease-in-out infinite;
}
@keyframes mcpPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.65; }
}
body.staff-impersonating .maintenance-countdown-pill {
  top: calc(var(--titlebar-height) + var(--staff-banner-height) + 8px);
}
body:has(.pinned-flight-strip) .maintenance-countdown-pill {
  top: calc(var(--titlebar-height) + 60px);
}
body.staff-impersonating:has(.pinned-flight-strip) .maintenance-countdown-pill {
  top: calc(var(--titlebar-height) + var(--staff-banner-height) + 60px);
}

.titlebar-logo {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  letter-spacing: 0.5px;
}

.titlebar-logo svg { color: var(--accent-blue); }

.titlebar-status {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-muted);
  font-family: 'IBM Plex Mono', monospace;
}

.titlebar-utc {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent-blue);
  letter-spacing: 0.5px;
  -webkit-app-region: no-drag;
}

/* World-connection orbit indicator (titlebar). Small globe + orbiting
   satellite when online; same globe with a diagonal slash when offline;
   faster orbit in orange while loading. The whole SVG is colored via
   `currentColor` so the parent class drives the palette per state. */
.world-orbit {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
  transition: color var(--transition-normal);
  -webkit-app-region: no-drag;
}
.world-orbit svg {
  display: block;
  overflow: visible;
  filter: drop-shadow(0 0 3px currentColor);
}

/* Custom hover tooltip — replaces the native HTML `title` so we get
   instant appearance, multi-line layout and themed styling. */
.world-orbit-tooltip {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  display: none;
  flex-direction: row;
  align-items: baseline;
  gap: 0;
  padding: 6px 10px;
  background: var(--bg-elevated, #1a1a1a);
  border: 1px solid var(--border-subtle);
  border-radius: 4px;
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 11px;
  line-height: 1.35;
  white-space: nowrap;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
  z-index: 9999;
  pointer-events: none;
  filter: none;
}
.world-orbit:hover .world-orbit-tooltip,
.world-orbit:focus-visible .world-orbit-tooltip { display: flex; }
.world-orbit-tooltip-state { font-weight: 600; letter-spacing: 0.2px; }
.world-orbit-tooltip-state.online  { color: var(--accent-green); }
.world-orbit-tooltip-state.loading { color: var(--accent-orange); }
.world-orbit-tooltip-state.offline { color: var(--accent-red); }
.world-orbit-tooltip-world { color: var(--text-secondary); }
/* Backend environment (production / local / Prod 2). Color comes inline from
   SERVER_MODE_CHOICES so the tooltip and the titlebar chip always agree.
   The margin is not decoration: the tooltip is a flex row, so the leading
   space of this item is trimmed and the separator would collide with the
   world name ("Stratus· Prod 2"). */
.world-orbit-tooltip-env { font-weight: 600; margin-left: 4px; }

/* Backend environment chip (titlebar) — only rendered when we are NOT on
   production, so its mere presence is the signal. `--env-color` is set
   inline per environment (amber = local, purple = Prod 2 REST). */
.titlebar-env-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 12px;
  padding: 2px 7px;
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.5px;
  line-height: 1.4;
  white-space: nowrap;
  color: var(--env-color, var(--accent-orange));
  background: color-mix(in srgb, var(--env-color, #f59e0b) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--env-color, #f59e0b) 45%, transparent);
  border-radius: 3px;
  cursor: default;
  -webkit-app-region: no-drag;
}
.world-orbit .world-orbit-ring   { fill: none; stroke: currentColor; stroke-width: 0.6; opacity: 0.25; }
.world-orbit .world-orbit-globe  { fill: currentColor; }
.world-orbit .world-orbit-sat    { transform-origin: 8px 8px; }
.world-orbit .world-orbit-sat circle { fill: currentColor; }
.world-orbit .world-orbit-slash  { display: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }

.world-orbit.online  { color: var(--accent-green); }
.world-orbit.loading { color: var(--accent-orange); }
/* The satellite only turns while the connection is loading. It used to turn all
   the time, in every state: a CSS-animated SVG child cannot be composited, so the
   16 px spinner re-ran style + layout ~300 times a second on EVERY page and kept
   the renderer's main thread ~30 % busy at idle (measured 2026-10-06 on the
   dashboard and Company Jobs: 29-33 % with it, 0.2 % paused). */
.world-orbit.loading .world-orbit-sat { animation: world-orbit-spin 0.9s linear infinite; }
.world-orbit.offline { color: var(--accent-red); }
.world-orbit.offline .world-orbit-globe { fill: none; stroke: currentColor; stroke-width: 1; }
.world-orbit.offline .world-orbit-ring  { opacity: 0; }
.world-orbit.offline .world-orbit-sat   { display: none; }
.world-orbit.offline .world-orbit-slash { display: block; }

@keyframes world-orbit-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .world-orbit .world-orbit-sat,
  .world-orbit.loading .world-orbit-sat { animation: none; }
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

/* Inline tracking pill in the titlebar — sits in the right cluster, just
   left of the UTC clock. One-click jump to the active flight tracker.
   The accent color is driven by --tk-accent (set inline from the current
   flight phase). */
.titlebar-tracking-pill {
  -webkit-app-region: no-drag;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.4px;
  font-family: 'IBM Plex Sans', sans-serif;
  white-space: nowrap;
  background: color-mix(in srgb, var(--tk-accent) 22%, transparent);
  color: var(--tk-accent);
  border: 1.5px solid color-mix(in srgb, var(--tk-accent) 70%, transparent);
  border-radius: 999px;
  box-shadow:
    0 0 10px color-mix(in srgb, var(--tk-accent) 45%, transparent),
    inset 0 0 0 1px color-mix(in srgb, var(--tk-accent) 25%, transparent);
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.titlebar-tracking-pill:hover:not(:disabled) {
  transform: scale(1.05);
  background: color-mix(in srgb, var(--tk-accent) 32%, transparent);
  box-shadow:
    0 0 18px color-mix(in srgb, var(--tk-accent) 65%, transparent),
    inset 0 0 0 1px color-mix(in srgb, var(--tk-accent) 35%, transparent);
}
.titlebar-tracking-pill:active:not(:disabled) {
  transform: scale(0.98);
}
.titlebar-tracking-pill:disabled {
  opacity: 0.55;
  cursor: default;
}
.titlebar-tracking-pill-dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--tk-accent);
  box-shadow: 0 0 8px var(--tk-accent);
  animation: pulse 1.4s infinite;
}
.titlebar-tracking-pill:disabled .titlebar-tracking-pill-dot {
  animation: none;
}
.titlebar-tracking-pill-arrow {
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  opacity: 0.85;
  transition: transform 0.15s ease, opacity 0.15s ease;
}
.titlebar-tracking-pill:hover:not(:disabled) .titlebar-tracking-pill-arrow {
  transform: translateX(3px);
  opacity: 1;
}
/* Disabled = you're already on the tracking view. Keep the trailing glyph
   (now a static "here" dot, not the → arrow) instead of hiding it, so the
   two states differ by SHAPE, not just by the accent glow a colour-blind
   user can't see (tester feedback, June 2026). */
.titlebar-tracking-pill:disabled .titlebar-tracking-pill-arrow {
  font-size: 11px;
  opacity: 0.6;
}
/* Active (clickable) pill: underline the label as a colour-independent
   "this is a button you can click" cue — the accent glow that normally
   signals clickability is invisible to colour-blind users. Dropped when
   disabled (you're already there). */
.titlebar-tracking-pill:not(:disabled) .titlebar-tracking-pill-label {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

/* ==================== TITLEBAR — narrow-window compaction ====================
   The titlebar is a fixed 36px strip holding two flex clusters. Below ~1500px
   the content no longer fits, and because flex items may shrink past their
   content width the labels used to WRAP — which on a 36px bar means the top
   half of every caption gets clipped ("OnAir / Companion for Windows",
   "22 Jul / 08:01:49 / UTC"). Tester report, July 2026.

   Two rules govern the fix:
     1. Nothing in the titlebar is allowed to wrap, ever (safety net below).
        The single flexible element is the wordmark, which ellipsises.
     2. Content is shed in priority order as the window narrows, so the state
        indicators (tracking phase, connection, LIVE radio) outlive the
        decorations. Two tiers, calibrated against the 1200px minWidth
        enforced in main.js.
   Every fragment dropped here is still reachable: each control keeps its
   native title tooltip, and the values also live on the pages themselves. */
.titlebar-cluster {
  display: flex;
  align-items: center;
  gap: 16px;
}
/* Right cluster = live state. It never shrinks; the left one absorbs. */
.titlebar-cluster-right { flex-shrink: 0; }
/* …but "the left one absorbs" must mean the WORDMARK absorbs, not the mail /
   chat buttons: as flex items they default to shrink:1 and were collapsing to
   zero width (invisible) before the wordmark had finished ellipsising. */
.titlebar-cluster-left { min-width: 0; }
.titlebar-cluster-left > .titlebar-comm-btn { flex-shrink: 0; }
/* Last-resort containment: if even the compacted layout doesn't fit (staff
   worst case — QA rec pill + sim-pause + clock-health + VATSIM + tracking all
   at once at 1200px), the non-shrinking buttons would otherwise render OUTSIDE
   their collapsed cluster box and overlap the right one. Clip instead. Needs
   the full 36px height so the -5px unread badges stay inside the padding box. */
.titlebar-cluster-left { height: 100%; overflow: hidden; }
/* Dispatch + Radio button pairs. */
.titlebar-group {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  -webkit-app-region: no-drag;
}

/* Safety net — no wrapping anywhere, whatever the locale or window size.
   Deliberately blanket (`*`): the titlebar holds only chrome, no prose, and
   the conditional chips (sim-pause, clock-health, VATSIM, QA rec) are plain
   divs mounted from their own components — an allowlist of classes here would
   silently miss the next one added. */
.titlebar { flex-wrap: nowrap; }
.titlebar, .titlebar * { white-space: nowrap; }
/* …with the wordmark as the one designated truncating element. */
.titlebar-logo { min-width: 0; overflow: hidden; }
.titlebar-logo img { flex-shrink: 0; }
.titlebar-logo-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Tier 1 — shed redundant qualifiers. Everything still readable. */
@media (max-width: 1500px) {
  .titlebar-logo-platform,   /* "OnAir Companion" — the " for Windows" part */
  .titlebar-utc-date,        /* "22 Jul " — keep the clock, drop the date */
  .titlebar-sim-ac,          /* " · Dornier 228" — also shown in tracking */
  .titlebar-radio-icao {     /* " · LFGA" */
    display: none;
  }
}

/* Tier 2 — icon-only for the labelled chips; covers 1200→1320px. The Dispatch
   VU meter and the 📻 glyph carry the identity on their own, and the tracking
   pill keeps its label (only the phase suffix goes). */
@media (max-width: 1320px) {
  .titlebar-cluster { gap: 10px; }
  .titlebar-logo-text,
  .titlebar-btn-label,
  .titlebar-env-chip-label,   /* "PROD 2" — emoji + color still identify it */
  .titlebar-tracking-pill-phase {
    display: none;
  }
  .titlebar-tracking-pill { padding: 4px 10px; gap: 6px; }
  .titlebar-env-chip { margin-left: 8px; padding: 2px 5px; gap: 0; }
}

/* ==================== SIDEBAR ==================== */
.sidebar {
  position: fixed;
  top: var(--titlebar-height);
  left: 0;
  bottom: 0;
  width: var(--sidebar-width);
  background: var(--bg-secondary);
  border-right: 1px solid var(--border-subtle);
  padding: 12px 8px;
  overflow-y: auto;
  z-index: 100;
  display: flex;
  flex-direction: column;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 16px 12px;
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: 0.3px;
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: 8px;
}

/* Sidebar logo — switches between dark/day based on theme mode */
.sidebar-logo-wrap { border-radius: 8px; padding: 8px 12px; width: 85%; display: flex; justify-content: center; }
.sidebar-logo { width: 100%; max-width: 160px; object-fit: contain; }
.sidebar-logo-day { display: none; }
[data-mode="light"] .sidebar-logo-wrap { }
[data-mode="light"] .sidebar-logo-dark { display: none; }
[data-mode="light"] .sidebar-logo-day { display: block; }

.sidebar-section { margin-bottom: 8px; }
.sidebar-bottom { margin-top: auto; }

.sidebar-label {
  font-size: 10px;
  font-weight: 700;
  color: var(--text-muted);
  letter-spacing: 1.5px;
  padding: 8px 12px 4px;
  font-family: 'IBM Plex Mono', monospace;
}

.nav-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 12px;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 13.5px;
  font-weight: 500;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: var(--transition-fast);
  text-align: left;
}

.nav-btn svg {
  width: 17px; height: 17px;
  flex-shrink: 0;
  stroke-width: 1.8;
}

.nav-btn:hover {
  background: rgba(255,255,255,0.05);
  color: var(--text-primary);
}

.nav-btn.active {
  background: rgba(59,130,246,0.12);
  color: var(--accent-blue);
}

.nav-btn.active svg { stroke: var(--accent-blue); }

/* ── Rail + panel sidebar (Option A) ──
 * Section icons live in a fixed left rail; the panel on the right shows the
 * items of the currently selected section. Switching sections only swaps
 * the panel content, never the layout, so items stay put under the cursor.
 */
.sidebar-rail-panel {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  overflow: hidden;
}
.sidebar-rail {
  flex: 0 0 52px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 4px 0;
  border-right: 1px solid var(--border-subtle);
  overflow-y: auto;
  overflow-x: hidden;
}
.sidebar-rail-btn {
  position: relative;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  border-left: 2px solid transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: var(--transition-fast);
}
.sidebar-rail-btn svg { width: 20px; height: 20px; flex-shrink: 0; }
.sidebar-rail-btn:hover { color: var(--text-primary); background: rgba(255,255,255,0.03); }
.sidebar-rail-btn.has-active { color: var(--accent-blue); }
.sidebar-rail-btn.active {
  color: var(--accent-blue);
  background: rgba(59,130,246,0.10);
  border-left-color: var(--accent-blue);
}
[data-mode="light"] .sidebar-rail-btn:hover { background: rgba(0,0,0,0.03); }
.sidebar-rail-badge {
  position: absolute;
  top: 6px; right: 6px;
  min-width: 14px; height: 14px;
  border-radius: 7px;
  background: #ef4444;
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  padding: 0 3px;
}
.sidebar-panel {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.sidebar-panel-header {
  flex-shrink: 0;
  padding: 10px 12px 6px;
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border-subtle);
}
.sidebar-panel-items {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  padding: 4px 0;
}

/* Sidebar menu help — small "?" affordance above the rail+panel.
   Lives outside the rail so it stays put in both layouts. The wrap gets a
   bottom border to separate it visually from the rail beneath; the label is
   hidden by the collapsed-mode rule below (mirrors .nav-btn-label). */
.sidebar-menu-help-wrap {
  flex-shrink: 0;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border-subtle);
}
.sidebar-menu-help-btn {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 6px 8px;
  background: transparent; border: 1px dashed var(--border-subtle);
  border-radius: 6px;
  color: var(--text-muted); font-size: 11px; font-weight: 600;
  cursor: pointer; transition: var(--transition-fast);
  font-family: 'IBM Plex Sans', sans-serif; text-align: left;
}
.sidebar-menu-help-btn:hover {
  background: rgba(59,130,246,0.06);
  border-color: var(--accent-blue);
  color: var(--accent-blue);
}
.sidebar-menu-help-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
[data-mode="light"] .sidebar-menu-help-btn:hover { background: rgba(59,130,246,0.08); }

/* Sub-page button inside a section panel */
.nav-sub-btn {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 6px 12px;
  background: transparent; border: none;
  color: var(--text-muted); font-size: 12px; font-weight: 400;
  cursor: pointer; transition: var(--transition-fast);
  font-family: 'IBM Plex Sans', sans-serif; text-align: left;
}
.nav-sub-btn:hover { background: rgba(255,255,255,0.04); color: var(--text-primary); }
.nav-sub-btn.active { color: var(--accent-blue); background: rgba(59,130,246,0.08); font-weight: 500; }
.nav-sub-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
[data-mode="light"] .nav-sub-btn:hover { background: rgba(0,0,0,0.04); }

/* ==================== SIDEBAR — COLLAPSED MODE (desktop) ====================
 * Toggle button at the bottom of the rail flips `body.sidebar-collapsed`.
 * That class re-points `--sidebar-width` at the collapsed value so
 * .main-content shifts via its existing `left: var(--sidebar-width)` rule.
 * Labels/panel are hidden; the rail stays visible. Sections open in a flyout
 * panel overlaid to the right of the rail (does not push main-content).
 * Mobile drawer (≤ 768px) is untouched — the media query below scopes
 * everything to ≥ 769px. */

.sidebar { transition: width 0.2s ease; }
.main-content { transition: left 0.2s ease; }

.sidebar-collapse-toggle {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 26px;
  background: transparent;
  border: none;
  border-top: 1px solid var(--border-subtle);
  color: var(--text-muted);
  cursor: pointer;
  transition: var(--transition-fast);
}
.sidebar-collapse-toggle:hover { color: var(--text-primary); background: rgba(255,255,255,0.04); }
.sidebar-collapse-toggle svg { width: 14px; height: 14px; }
[data-mode="light"] .sidebar-collapse-toggle:hover { background: rgba(0,0,0,0.04); }

@media (min-width: 769px) {
  body.sidebar-collapsed { --sidebar-width: var(--sidebar-width-collapsed); }

  /* Hide the inline (rail-panel) panel — only the flyout (.sidebar-panel.flyout)
     remains visible. `:not(.flyout)` is required because the flyout panel
     keeps the .sidebar-panel class, and a bare `> .sidebar-panel` rule wins
     specificity over `.sidebar-panel.flyout { display: flex }` further down. */
  body.sidebar-collapsed .sidebar-rail-panel > .sidebar-panel:not(.flyout) { display: none; }
  body.sidebar-collapsed .sidebar-rail { border-right: none; }
  body.sidebar-collapsed .sidebar { padding: 12px 0; }

  /* Brand strip — keep just the small companion icon centered. */
  body.sidebar-collapsed .sidebar-brand { padding: 8px 4px; gap: 0; border-bottom: none; }
  body.sidebar-collapsed .sidebar-logo-wrap,
  body.sidebar-collapsed .sidebar-brand .sidebar-build-strip,
  body.sidebar-collapsed .sidebar-brand > div > span { display: none; }

  /* Hide mode pill + pilot location wrappers — user re-expands to access them. */
  body.sidebar-collapsed .sidebar-mode-pill-wrap,
  body.sidebar-collapsed .sidebar-pilot-wrap { display: none; }

  /* VA switch (web only — see components/va-switch-button.jsx) — icon ONLY,
     never hidden. It does NOT join the two wrappers above: those are a status
     pill and a shortcut, both reachable elsewhere, whereas this button is the
     one and only way into VA mode in the web build (#12070). Hiding it in the
     collapsed rail would put the web build straight back where it was. Same
     treatment as .nav-btn instead: keep the control, drop the label. */
  body.sidebar-collapsed .sidebar-va-switch-wrap { padding: 6px 4px; }
  body.sidebar-collapsed .sidebar-va-switch-wrap > button { gap: 0; padding: 7px 0; }
  body.sidebar-collapsed .sidebar-va-switch-label { display: none; }

  /* UTC clock (web only — #12084) — never hidden either: in the web build the
     sidebar is the clock's only mount. "06:51:00 UTC" does not fit 56 px on
     one line, so drop the date and let the time and "UTC" wrap onto two. */
  body.sidebar-collapsed .sidebar-utc { margin-top: 6px; }
  body.sidebar-collapsed .sidebar-utc .titlebar-utc-date { display: none; }
  body.sidebar-collapsed .sidebar-utc .titlebar-utc {
    font-size: 9px;
    letter-spacing: 0;
    line-height: 1.25;
    white-space: normal;
    text-align: center;
  }

  /* Lobby / Settings buttons — icon only. */
  body.sidebar-collapsed .nav-btn { justify-content: center; padding: 9px 0; gap: 0; }
  body.sidebar-collapsed .nav-btn-label { display: none; }

  /* Sidebar menu help — icon-only in collapsed mode. The wrap loses its
     horizontal padding so the dashed button is centered in the 56 px rail. */
  body.sidebar-collapsed .sidebar-menu-help-wrap { padding: 6px 4px; }
  body.sidebar-collapsed .sidebar-menu-help-btn { justify-content: center; gap: 0; padding: 6px 0; }
  body.sidebar-collapsed .sidebar-menu-help-label { display: none; }

  /* Flyout panel — overlay to the right of the rail, doesn't push main-content.
     `position: fixed` (not absolute) is intentional: the .sidebar parent has
     `overflow-y: auto` which per spec promotes overflow-x from `visible` to
     `auto` — that would clip an absolutely-positioned flyout extending past
     the sidebar's right edge. `fixed` escapes the overflow clipping by
     resolving against the viewport. */
  .sidebar-panel.flyout {
    position: fixed;
    left: var(--sidebar-width);
    top: var(--titlebar-height);
    bottom: 0;
    width: 220px;
    background: var(--bg-secondary);
    border-right: 1px solid var(--border-subtle);
    box-shadow: 4px 0 16px rgba(0,0,0,0.35);
    /* High enough to clear the pinned-flight-strip (z-index 9998) when a
       flight is in progress. Modals are typically 10000+ so they still win. */
    z-index: 9999;
    display: flex;
    flex-direction: column;
  }
  /* Visual cue on the rail icon that owns the currently open flyout — the
     existing `.active` style is reserved for the persisted activeSectionId
     so we use a distinct (slightly stronger) accent here. */
  .sidebar-rail-btn.flyout-open {
    color: var(--accent-blue);
    background: rgba(59,130,246,0.14);
    border-left-color: var(--accent-blue);
  }
}

/* Staff "Log As" banner pushes the sidebar down by 28 px — the flyout has
   to follow so its top edge aligns with the sidebar. */
body.staff-impersonating .sidebar-panel.flyout {
  top: calc(var(--titlebar-height) + 28px);
}

/* ==================== MAIN CONTENT ==================== */
.main-content {
  position: fixed;
  top: var(--titlebar-height);
  left: var(--sidebar-width);
  right: 0;
  bottom: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 16px 20px;
  scroll-behavior: smooth;
}

/* ── VA mode — the persistent "you are your VA right now" signal ──
   A 2 px rule across the top of the content area, on every page. Painted as an
   INSET shadow rather than a border so it costs zero layout: .main-content is
   position:fixed, so the line stays put while the page scrolls under it.
   It exists because the expensive mistakes (hiring, buying, accepting a job)
   happen on screens that show nothing VA-specific — the switcher pill alone
   asks the player to remember, and this does not. */
body.va-mode .main-content {
  box-shadow: inset 0 2px 0 0 var(--va-accent);
}
/* Same idea on the sidebar's left edge, so the signal survives a page whose
   content is a full-bleed map and the top rule is easy to miss. */
body.va-mode .sidebar {
  box-shadow: inset 2px 0 0 0 var(--va-accent);
}

.main-content::-webkit-scrollbar { width: 6px; }
.main-content::-webkit-scrollbar-track { background: transparent; }
.main-content::-webkit-scrollbar-thumb { background: var(--border-medium); border-radius: 3px; }

/* ── Floor of a full-height page's list area (ticket #12125) ──
   A FULL_HEIGHT_PAGES page is exactly as tall as its bounded wrapper, its header
   blocks are flex-shrink:0, and its list area (`flex:1`) is the only thing left
   to shrink. With `min-height:0` it went to ZERO on a short screen — a laptop
   browser, or the desktop app near its 700 px minimum: jobs found and none
   visible, routes reduced to their table header, and `.main-content` with
   nothing to scroll. The floor makes the page outgrow its wrapper instead, so
   `.main-content` scrolls; on a normal screen the area is taller than the floor
   and nothing changes. 360 px = a list toolbar + header + ~6 rows.
   ⚠️ An inline `min-height:0` on the same element beats this class: the class
   only works where the inline one has been removed. */
.list-area-floor { min-height: 360px; }

/* ==================== SCREEN LAYOUTS ==================== */
.screen { display: none; animation: fadeIn 0.3s ease; }
.screen.active { display: block; }

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

.dashboard-brand {
  margin-bottom: 20px;
}
.dashboard-logo {
  height: 40px;
  width: auto;
  opacity: 0.9;
}
[data-mode="light"] .dashboard-logo {
  filter: brightness(0.3);
}

.screen-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 8px;
  margin-bottom: 20px;
}

.screen-title {
  font-size: 24px;
  font-weight: 700;
  color: var(--text-bright);
  letter-spacing: -0.5px;
}

.screen-subtitle {
  font-size: 13px;
  color: var(--text-muted);
  margin-top: 4px;
}

.screen-actions {
  display: flex;
  gap: 8px;
}

/* ==================== CARDS ==================== */
.card {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 20px;
  box-shadow: var(--bevel-shadow);
  transition: var(--transition-normal);
  min-width: 0;
  overflow: hidden;
}

.card:hover {
  border-color: var(--border-medium);
  box-shadow: var(--shadow-card);
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}

.card-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.card-subtitle {
  font-size: 11px;
  color: var(--text-muted);
  font-weight: 500;
}

/* ── Flight stats mini-grid (dashboard) ── */
.flight-stats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-bottom: 16px;
  padding: 12px;
  background: var(--bg-secondary);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
}

.flight-stat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.flight-stat-value {
  font-size: 20px;
  font-weight: 700;
  font-family: 'IBM Plex Mono', monospace;
  letter-spacing: -0.5px;
}

.flight-stat-label {
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.3px;
  text-align: center;
}

.card-value {
  font-size: 28px;
  font-weight: 700;
  color: var(--text-bright);
  font-family: 'IBM Plex Mono', monospace;
  letter-spacing: -1px;
}

.card-change {
  font-size: 12px;
  font-weight: 500;
  margin-top: 6px;
}

.card-change.positive { color: var(--accent-green); }
.card-change.negative { color: var(--accent-red); }

/* ==================== GRID LAYOUTS ==================== */
.grid-4 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  margin-bottom: 24px;
}

.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-bottom: 24px;
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
  margin-bottom: 24px;
}

.grid-2-1 {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 16px;
  margin-bottom: 24px;
}

.grid-1-2 {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 16px;
  margin-bottom: 24px;
}

.full-width { grid-column: 1 / -1; }

/* ==================== STAT CARDS ==================== */
.stat-card {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 20px;
  position: relative;
  overflow: hidden;
  box-shadow: var(--bevel-shadow);
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
/* Hover lift on clickable stat-cards — intensifies the bottom accent stripe */
.stat-card.clickable {
  cursor: pointer;
  user-select: none;
}
.stat-card.clickable:hover {
  transform: translateY(-1px);
  border-color: var(--border-medium);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.06),
    inset 0 -1px 0 rgba(0,0,0,0.3),
    0 4px 14px rgba(0,0,0,0.45);
}
.stat-card.clickable:hover::before {
  opacity: 1;
  height: 4px;
}
[data-mode="light"] .stat-card.clickable:hover {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.7),
    inset 0 -1px 0 rgba(0,0,0,0.06),
    0 3px 10px rgba(0,0,0,0.1);
}

.stat-card::before {
  content: '';
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 3px;
  background: var(--accent-color, var(--accent-blue));
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  opacity: 0.8;
}

.stat-card .stat-icon {
  width: 36px; height: 36px;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 12px;
  font-size: 18px;
}

.stat-card .stat-label {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.8px;
  margin-bottom: 6px;
}

.stat-card .stat-value {
  font-size: 26px;
  font-weight: 700;
  color: var(--text-bright);
  font-family: 'IBM Plex Mono', monospace;
  letter-spacing: -0.5px;
}

.stat-card .stat-detail {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 4px;
}
.stat-card .stat-trend {
  margin-top: 8px;
  height: 18px;
  line-height: 0;
}

/* ==================== QUICK ACTIONS (dashboard) ==================== */
/* Task-oriented entry row — see src/pages/dashboard/quick-actions.jsx.
   Each button mirrors the .stat-card.clickable hover idiom but with a
   left accent stripe (driven by the inline --accent-color) instead of a
   bottom one, and a horizontal icon + label layout. */
.quick-action-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  width: 100%;
  text-align: left;
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--bevel-shadow);
  cursor: pointer;
  user-select: none;
  position: relative;
  overflow: hidden;
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.quick-action-btn::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 3px;
  background: var(--accent-color, var(--accent-blue));
  opacity: 0.85;
}
.quick-action-btn:hover {
  transform: translateY(-1px);
  border-color: var(--border-medium);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.06),
    inset 0 -1px 0 rgba(0,0,0,0.3),
    0 4px 14px rgba(0,0,0,0.45);
}
.quick-action-btn:hover::before { width: 4px; opacity: 1; }
[data-mode="light"] .quick-action-btn:hover {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.7),
    inset 0 -1px 0 rgba(0,0,0,0.06),
    0 3px 10px rgba(0,0,0,0.1);
}
.quick-action-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.quick-action-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.2;
}

/* Department hub widget — section-shortcut chips (dashboard-hub.jsx). */
.hub-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 8px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--bg-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.hub-chip:hover {
  background: var(--bg-card);
  border-color: var(--border-medium);
  color: var(--text-primary);
}

/* Compact department tile — icon + name + count, opens a flyout on click. */
.hub-dept-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 14px 10px 12px;
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  cursor: pointer;
  text-align: center;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.hub-dept-tile:hover { background: var(--bg-card-hover); border-color: var(--border-medium); }
.hub-dept-tile.open { background: var(--bg-card-hover); }

/* Floating department flyout — portaled to <body>, overlays content below. */
.hub-dept-flyout {
  position: fixed;
  z-index: 9999;
  background: var(--bg-elevated);
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-elevated);
  padding: 12px;
  animation: hover-card-in 0.15s ease-out;
}

/* ==================== TABLES ==================== */
.table-container {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--bevel-shadow);
}

.table-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border-subtle);
}

.table-toolbar-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
}

.table-scroll {
  overflow-x: auto;
  overflow-y: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

/* .data-table: standalone tables (not wrapped in .table-container) get a
 * semi-opaque backdrop so the ambient map stays subtle behind the rows.
 * Less opaque than the full card background but enough for legibility. */
.data-table {
  background: color-mix(in srgb, var(--bg-card) 82%, transparent);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  /* `clip`, NOT `hidden`, and the difference is the whole point.
   *
   * Both clip the row corners to the radius above, which is the only reason
   * this line exists. But `hidden` also makes the element a SCROLL CONTAINER,
   * and `thead` is `position:sticky` app-wide (see below). A sticky element
   * sticks to its nearest scroll container — so on every `.data-table` that
   * sits inside a scrolling wrapper, the header was sticking to the table
   * itself, which never scrolls, and rode straight out of the viewport.
   * Measured on the Trading Assistant, 2026-08-07: header at -1730px against a
   * scroller whose top was at +247px. Five other sites had the same montage.
   * `overflow: clip` clips without establishing a scroll container, which is
   * exactly what a decorative radius needs. */
  overflow: clip;
}
[data-mode="light"] .data-table {
  background: color-mix(in srgb, var(--bg-card) 88%, transparent);
}

thead {
  position: sticky;
  top: 0;
  z-index: 10;
}

thead th {
  background: var(--bg-elevated);
  padding: 6px 10px;
  text-align: left;
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border-subtle);
  white-space: nowrap;
  font-family: 'IBM Plex Mono', monospace;
  cursor: pointer;
  user-select: none;
}

thead th:hover { color: var(--text-secondary); }

tbody tr {
  border-bottom: 1px solid var(--border-subtle);
  transition: var(--transition-fast);
  line-height: 1.3;
}

/* One token for both modes — the light-mode twin this rule used to need is
   gone, the ink flips with `--row-hover-overlay`. A `<tr>` carrying its own
   inline background still wins over this (6 rows app-wide, all of them
   painting a state): they keep their state and lose the hover, which is the
   right way round. */
tbody tr:hover { background: var(--row-hover-overlay); }

tbody td {
  padding: 4px 10px;
  color: var(--text-secondary);
  white-space: nowrap;
  font-size: 12px;
}

/* Opt-in escape from the `nowrap` directly above, for the rare cell the server
 * fills with PROSE instead of a figure — today: RegularRouteFlight.Comment.
 *
 * That field is an append-only log (RegularRoutesBusiness.UpdateRouteFlightStatus
 * does `Comment += "\n" + comment`), and a failed occurrence appends the whole
 * 12-line "Aircraft is overloaded / Fuel / Gross Weight …" block thrown by
 * FlightBusiness.EnsureWeights. Measured in the running app on v1.0.379 (ticket
 * Goofeyfoot, 2026-08-12): the 12 newlines collapsed to spaces and the log
 * rendered as ONE 1593px line inside a 786px viewport — 1196px off-screen, with
 * no title attribute to fall back on. The tester's words were "a long
 * spaghetti-like string of text wanders off the page".
 *
 * The height clamp is not decoration: with pre-wrap alone the same content
 * makes the row 195px tall (23 lines), so a single failed occurrence swallows
 * two thirds of the 300px-high Scheduled Flights table. Clamped it is 65px and
 * the rest is reachable by scrolling inside the cell.
 *
 * Do NOT fix this by touching the `tbody td` rule above — it holds every table
 * in the app, where nowrap is the right default. */
tbody td.cell-prose {
  white-space: normal;
  min-width: 220px;
}
/* One-line journal summary inside an occurrence row. Replaces the old
   `.cell-prose-text`, which printed the server's WHOLE multi-line log into a
   64 px box with an inner scrollbar — on a refused flight that was six lines
   behind a scrollbar a few pixels tall (ticket Goofeyfoot, 2026-08-15). The
   row now shows the last entry and a button opens the rest
   (components/flight-journal-modal.jsx).
   `min-width: 0` is load-bearing: the cell is a flex item next to that
   button, and without it the ellipsis never triggers — the text pushes the
   button out of the cell instead. */
.cell-journal-line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 1;
  line-height: 1.35;
}
/* Muted second line under a cell's main value — the real departure of a route
   occurrence under its planned slot (ticket #10485), on both occurrence tables.
   ⚠️ `white-space: normal` is the load-bearing half: `tbody td` is nowrap
   app-wide (a rule route-flight-comment-cell.test.mjs deliberately keeps), so a
   sentence would WIDEN its cell instead of wrapping — and one of the two hosts
   is half of a `1fr 1fr` grid with no horizontal scroll. It lives in CSS rather
   than in two inline copies precisely so it cannot be dropped on one side.
   ⚠️ Deliberately NOT scoped to `td`: the class is named for a role, so the next
   surface that reuses it must get the whole contract — wrapping included — and
   not silently lose the half that matters. `font-family` is here for the same
   reason: one of the two hosts is a `mono` cell, and prose in a monospace face
   is both wider and wrong. */
.cell-subline {
  white-space: normal;
  font-family: 'IBM Plex Sans', -apple-system, sans-serif;
  font-size: 10px;
  line-height: 1.3;
  color: var(--text-muted);
}
/* A scheduled slot still reading In Progress long after its engines were shut
   down (ticket #10558). Orange and not red, following the convention this
   project mirrors from the WPF client: red = stopped/lost, orange = degraded
   but recoverable. Deactivating and reactivating the route does recover it. */
.cell-subline.is-stalled {
  color: var(--accent-orange);
}
/* TYPE cell of the dispatch « Cargo & Passengers at Airport » tables
   (flight-management/cargo-charter-section.jsx). The label is server prose
   ("University professor (Northwestern) of biomedical engineering…") and was
   clipped at 260 px with an ellipsis, while `tbody td` nowrap pushed the action
   buttons past the card: reading a job, or reaching its buttons, meant
   scrolling sideways (user report 2026-09-16).
   ⚠️ `width: 100%` is load-bearing: in an auto-layout table it hands this column
   every pixel the nowrap figures beside it (weight, ICAO, distance, buttons) do
   not need, so the label wraps inside the card instead of widening the table.
   ⚠️ In CSS rather than inline because the cargo and the passenger tables are
   two copies of the same row — one class keeps them from drifting apart. */
tbody td.cell-payload-type {
  white-space: normal;
  width: 100%;
}
.cell-payload-type-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 3px 4px;
}
/* The label pill may wrap. `min-width: 0` lets it shrink below its one-line
   width, and a 10 px radius is still a full pill at one line (~19 px tall) but
   a rounded box, not an oval, at two. */
.cell-payload-type-inner > .badge {
  white-space: normal;
  overflow-wrap: anywhere;
  border-radius: 10px;
  min-width: 0;
}
/* Only the label wraps. The small tags after it (FLC, Tour, Delivered,
   ⇄ N123, ⏰ …) move to the next line whole, as they did when the cell was
   nowrap — never split between their icon and their text. */
.cell-payload-type-inner > :not(.badge) {
  white-space: nowrap;
}
/* A plain-text label (Work Orders payload table, registry R11 2026-09-17):
   same freedom as the pill above. Declared after the nowrap rule it overrides,
   at the same specificity. */
.cell-payload-type-inner > .cell-payload-label {
  white-space: normal;
  overflow-wrap: anywhere;
  min-width: 0;
}
/* Job code as a button (Flight Dispatch, ticket #12028): one click selects
   every row of the job across the Cargo and Passengers tables. Same mono
   5-character code as the passive label elsewhere (components/job-code.jsx),
   outlined so it reads as clickable; pressed = the whole job is selected. */
.job-code-btn {
  flex: 0 0 auto;
  margin-left: 6px;
  padding: 0 4px;
  border-radius: 3px;
  border: 1px solid var(--border-medium);
  background: transparent;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  color: var(--text-muted);
  cursor: pointer;
}
.job-code-btn:hover {
  color: var(--text-primary);
  border-color: var(--accent-blue);
}
.job-code-btn[aria-pressed="true"] {
  color: var(--accent-blue);
  border-color: var(--accent-blue);
  background: color-mix(in srgb, var(--accent-blue) 14%, transparent);
}

tbody td.mono {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
}
th.num, td.num {
  text-align: right;
}
td.num {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* ==================== FINANCIAL TABLE ==================== */
.financial-table tbody td {
  padding: 8px 16px;
  font-size: 13px;
}
.financial-table .financial-section td {
  background: var(--bg-elevated);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.5px;
  color: var(--accent-blue);
  text-transform: uppercase;
  padding: 10px 16px 6px;
  border-bottom: 2px solid var(--border-accent);
}

/* ==================== CASH FLOW TOOLBAR ==================== */
.cashflow-toolbar {
  display: flex;
  align-items: center;
  padding: 10px 20px;
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-bottom: none;
  gap: 12px;
  font-size: 13px;
}
.cashflow-toolbar-item {
  display: flex;
  align-items: center;
  gap: 8px;
}
.cashflow-toolbar-spacer { flex: 1; }
.cashflow-label {
  color: var(--text-muted);
  font-size: 12px;
}
.cashflow-value {
  color: var(--text-primary);
  font-weight: 600;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 13px;
}
.cashflow-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
.dot-green { background: var(--accent-green); }
.dot-red { background: var(--accent-red); }
.dot-yellow { background: var(--accent-yellow); }
.dot-blue { background: var(--accent-blue); }
.dot-purple { background: var(--accent-purple); }
.col-filter {
  font-size: 9px;
  color: var(--text-muted);
  margin-left: 4px;
  opacity: 0.5;
}
thead th:hover .col-filter { opacity: 1; }

/* ==================== ACCOUNT TABLE (Balance Sheet / Income) ==================== */
.acct-panel {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.acct-panel-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border-subtle);
}
.acct-panel-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--text-primary);
}
.acct-panel-total {
  margin-left: auto;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 16px;
  font-weight: 700;
}
.acct-table {
  width: 100%;
  border-collapse: collapse;
}
.acct-table th {
  padding: 8px 16px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border-subtle);
  font-weight: 600;
  font-family: 'IBM Plex Mono', monospace;
}
.acct-row td {
  padding: 8px 16px;
  border-bottom: 1px solid var(--border-subtle);
  font-size: 13px;
  color: var(--text-secondary);
}
.acct-row-expandable { cursor: pointer; }
/* No hover rule of its own any more: these rows live in a `<tbody>`, so the
   one `tbody tr:hover` above already tints them, with the ink that flips
   between modes. The 3% white this replaces stacked ON TOP of that global
   tint, and only in dark mode — it painted white on white on paper. */
.acct-expand-icon {
  display: inline-block;
  width: 20px;
  color: var(--accent-blue);
  font-size: 14px;
  font-weight: 700;
}
.acct-entry-row td {
  padding: 5px 16px 5px 32px;
  font-size: 12px;
  border-bottom: 1px solid rgba(255,255,255,0.03);
  color: var(--text-secondary);
  background: rgba(0,0,0,0.12);
}
.acct-entry-date {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px;
  margin-right: 12px;
  opacity: 0.6;
}
.acct-entry-desc { font-size: 12px; }

/* ==================== INCOME PAGE ==================== */
.income-page { display: flex; flex-direction: column; height: 100%; }
.income-header {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 20px;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border-subtle);
  flex-wrap: wrap;
}
.income-title {
  font-size: 18px;
  font-weight: 700;
  color: var(--text-primary);
  font-style: italic;
}
.income-date-picker {
  display: flex;
  align-items: center;
  gap: 8px;
}
.income-date-input {
  width: 130px !important;
  padding: 4px 8px !important;
  font-size: 12px !important;
}
.income-header-spacer { flex: 1; }
.income-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  padding: 8px;
  flex: 1;
  min-height: 0;
  align-items: start;
}
.income-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px;
  background: var(--bg-card);
  border-top: 1px solid var(--border-subtle);
  font-size: 14px;
  color: var(--text-secondary);
}
.income-footer strong {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 15px;
}

/* Ratio card styles */
.ratio-card {
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 24px; margin-bottom: 24px;
  box-shadow: var(--bevel-shadow);
  /* Column flex so two cards sitting side by side end up the SAME height.
     The ROA block carries four colour bands plus the recent-return panel
     against the Debt Ratio's three bands, so without this the two cards ended
     at visibly different depths. Needs the grid to stretch its items — see
     ratios.jsx (no align-items:start). */
  display: flex;
  flex-direction: column;
}
.ratio-card > .ratio-value-box {
  /* Symmetric auto margins centre the dial in whatever space is left over.
     On the taller card (ROA) there is none, so it stays put; on the shorter
     one the dial floats in the middle of the gap instead of being pinned to
     the bottom edge. This deliberately gives up a common dial baseline
     between the two cards — chosen over leaving one card visibly hollow. */
  margin-top: auto;
  margin-bottom: auto;
  align-self: flex-start; /* keep inline-flex's shrink-wrapped width */
}
.ratio-card h3 { color: var(--text-primary); margin: 0 0 10px; font-size: 16px; }
.ratio-card p { color: var(--text-secondary); font-size: 13px; line-height: 1.6; margin: 0 0 16px; }
.ratio-value-box {
  display: inline-flex; align-items: baseline; gap: 12px;
  border: 1px solid var(--border); border-radius: 8px; padding: 14px 20px;
  background: rgba(255,255,255,0.02);
}
/* Period return — the companion figure to the lifetime dial above it. */
.ratio-period {
  border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 14px; margin-bottom: 16px;
  background: rgba(255,255,255,0.02);
}
.ratio-period-title {
  font-size: 10px; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 6px;
}
.ratio-period-rows { display: flex; gap: 24px; flex-wrap: wrap; }
.ratio-period-item { display: inline-flex; align-items: baseline; gap: 8px; }
.ratio-period-label { font-size: 12px; color: var(--text-secondary); }
.ratio-period-val {
  font-size: 17px; font-weight: 700; font-family: 'IBM Plex Mono', monospace;
}
.ratio-period-note {
  font-size: 11px; color: var(--text-muted); margin-top: 6px; line-height: 1.5;
}

/* Asset composition — what the ROA denominator is made of. */
.asset-mix {
  margin-top: 24px; padding: 14px 18px;
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--bevel-shadow);
}
.asset-mix-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  margin-bottom: 8px;
}
.asset-mix-title {
  font-size: 11px; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-muted);
}
.asset-mix-total {
  font-size: 14px; font-weight: 700; font-family: 'IBM Plex Mono', monospace;
  color: var(--accent-cyan);
}
.asset-mix-bar {
  display: flex; height: 10px; border-radius: 5px; overflow: hidden;
  background: var(--border); gap: 1px;
}
.asset-mix-seg { height: 100%; min-width: 2px; }
.asset-mix-legend {
  display: flex; flex-wrap: wrap; gap: 8px 20px; margin-top: 10px;
}
.asset-mix-item { display: inline-flex; align-items: baseline; gap: 6px; font-size: 12px; }
.asset-mix-dot {
  width: 8px; height: 8px; border-radius: 2px; align-self: center; flex-shrink: 0;
}
.asset-mix-name { color: var(--text-secondary); }
.asset-mix-val { font-family: 'IBM Plex Mono', monospace; color: var(--text-primary); font-weight: 600; }
.asset-mix-pct { font-family: 'IBM Plex Mono', monospace; color: var(--text-muted); font-size: 11px; }

.ratio-formula { font-size: 13px; color: var(--text-muted); }
.ratio-value { font-size: 24px; font-weight: 700; font-family: 'IBM Plex Mono', monospace; }
.ratio-detail { font-size: 12px; color: var(--text-muted); font-family: 'IBM Plex Mono', monospace; }

/* ==================== BADGES / TAGS ==================== */
.badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 99px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3px;
}

.badge-green { background: rgba(16,185,129,0.15); color: var(--accent-green); }
.badge-blue { background: rgba(59,130,246,0.15); color: var(--accent-blue); }
.badge-orange { background: rgba(245,158,11,0.15); color: var(--accent-orange); }
.badge-red { background: rgba(239,68,68,0.15); color: var(--accent-red); }
.badge-purple { background: rgba(139,92,246,0.15); color: var(--accent-purple); }
.badge-cyan { background: rgba(6,182,212,0.15); color: var(--accent-cyan); }
.badge-gray { background: rgba(148,163,184,0.15); color: var(--text-muted); }

/* A badge inside a flight strip must fail VISIBLY (ticket #10573).
   `.fstrip` is overflow:hidden and a badge carries no ellipsis of its own, so
   a label wider than its cell was clipped mid-letter with nothing to say a
   word had been cut — the tester read "Maintenanc" and could not tell whether
   that was the status or a truncation. Widening the Fleet STATUS cell to
   110px fixes the case he hit; this is the net under every OTHER strip cell,
   at zero layout cost, for the labels nobody has measured yet (a longer
   translation, a new status, a narrow window shrinking the cell via
   `flex: 0 1`). Scoped to strips on purpose: badges elsewhere sit in
   content-sized containers and must keep growing to fit. */
.fstrip-cell .badge {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ==================== BUTTONS ==================== */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border: 1px solid var(--border-medium);
  background: var(--bg-elevated);
  color: var(--text-secondary);
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 13px;
  font-weight: 500;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: var(--transition-fast);
  -webkit-app-region: no-drag;
}

.btn:hover:not(:disabled) {
  background: var(--bg-card-hover);
  color: var(--text-primary);
  border-color: var(--border-accent);
}

/* A disabled .btn used to be visually IDENTICAL to an enabled one — a filled
   btn-primary stayed fully saturated and still lit up on hover. A tester read
   the Training "Start the exercise" button as broken ("I click and nothing
   happens") when it was simply gated (report 2026-08-03, EDTL). Same treatment
   as .btn-green:disabled, which already had it. */
.btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.btn-primary {
  background: var(--accent-blue);
  border-color: var(--accent-blue);
  color: var(--text-on-accent);
}

.btn-primary:hover:not(:disabled) {
  background: var(--accent-blue-dim);
  border-color: var(--accent-blue-dim);
  color: var(--text-on-accent);
}

.btn-green {
  background: var(--accent-green);
  border-color: var(--accent-green);
  color: var(--text-on-accent);
}

.btn-green:hover:not(:disabled) {
  background: var(--accent-green);
  border-color: var(--accent-green);
  color: var(--text-on-accent);
  filter: brightness(1.08);
}

/* Filled green reads as clickable — dim it clearly when disabled so a
   not-yet-actionable CTA (e.g. "Get a Quotation" before any change) doesn't
   look active. */
.btn-green:disabled {
  opacity: 0.4;
  filter: none;
  cursor: default;
}

.btn svg {
  width: 14px; height: 14px;
}

.btn-sm {
  padding: 5px 10px;
  font-size: 12px;
}

.btn-save {
  background: var(--accent-green);
  border-color: var(--accent-green);
  color: white;
  font-size: 14px;
  padding: 10px 28px;
  font-weight: 600;
  letter-spacing: 0.3px;
}
.btn-save:hover {
  background: #059669;
  border-color: #059669;
  color: white;
  box-shadow: 0 0 12px rgba(16,185,129,0.3);
}

.btn-dispatch {
  background: rgba(59,130,246,0.12);
  border-color: rgba(59,130,246,0.3);
  color: var(--accent-blue);
  white-space: nowrap;
}
.btn-dispatch:hover {
  background: rgba(59,130,246,0.25);
  border-color: var(--accent-blue);
  color: #93bbfd;
}

/* Pulsing halo for high-priority CTAs (Resume Tracking on aircraft lists).
   The orange brand colour is reused on many surrounding elements (ferry tags,
   skip outlines, class accents…), so the button alone doesn't pop. The pulse
   announces the action without changing layout (no scale/translate), and
   respects reduced-motion. */
/* A *blurred* glow (blur 6px, spread 1px) instead of a hard 4px-spread ring.
   The previous version overlapped sibling buttons sitting in tight strip rows
   (Map / Decline icons next to the Fly button), making the halo look offset
   to the right since the gap between buttons was the same width as the
   spread. A soft glow fades out before hitting the next button so the halo
   stays visually centred regardless of horizontal neighbours. */
/* The glow is a STATIC shadow on a pseudo-element and only its opacity pulses.
   Animating the button's own box-shadow was painted on the main thread every frame:
   ~10 % of it for ONE button and ~20 % from 30 (measured 2026-10-06) — and this
   class sits on every collapsed row of Pending Jobs. A glow fading in and out is what
   the shadow's alpha/size interpolation looked like anyway. */
@keyframes btn-pulse-cta {
  0%, 100% { opacity: 0; }
  50%      { opacity: 1; }
}
.btn-pulse-cta {
  position: relative;
}
.btn-pulse-cta::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: 0 0 6px 1px rgba(249,115,22,0.7);
  opacity: 0;
  animation: btn-pulse-cta 1.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .btn-pulse-cta::after { animation: none; }
}

/* Leftover-freight "last call" pulse. Bumped once when the pilot clicks
   Start Tracking (or auto-start fires) with leftover cargo/pax still parked
   at DEP. The amber border flashes 3× then settles back to its static state.
   Used together with playDeadlineAlarm() for a multi-modal cue. */
@keyframes freight-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(245,158,11,0); }
  20%      { box-shadow: 0 0 0 6px rgba(245,158,11,0.5); }
  40%      { box-shadow: 0 0 0 0 rgba(245,158,11,0); }
  60%      { box-shadow: 0 0 0 6px rgba(245,158,11,0.5); }
  80%      { box-shadow: 0 0 0 0 rgba(245,158,11,0); }
}
.freight-pulse {
  animation: freight-pulse 1.6s ease-in-out 1;
}
@media (prefers-reduced-motion: reduce) {
  .freight-pulse { animation: none; }
}

/* ==================== INPUTS ==================== */
.input-group {
  margin-bottom: 16px;
}

.input-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 6px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.input-field {
  width: 100%;
  padding: 10px 14px;
  background: var(--bg-input);
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 13px;
  transition: var(--transition-fast);
  outline: none;
}

.input-field:focus {
  border-color: var(--accent-blue);
  box-shadow: 0 0 0 3px rgba(59,130,246,0.1);
}

.input-field::placeholder { color: var(--text-muted); }

select.input-field {
  cursor: pointer;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%238892b0' viewBox='0 0 24 24'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 32px;
}

/* Explicit option styling — belt-and-suspenders with color-scheme: dark.
   Chromium honors background/color on <option> in the popup. Covers every
   <select> in the app, regardless of trigger styling (inline or class). */
option {
  background: var(--bg-card);
  color: var(--text-primary);
}

/* Les EN-TETES de groupe, que la regle ci-dessus ne couvrait pas — et sans elle
   Chromium les rend avec ses propres valeurs : dans le picker de repositionnement,
   « Runway ends » sortait en blanc sur blanc, donc illisible, juste au-dessus
   d'options parfaitement lisibles. L'italique par defaut du navigateur est
   retire lui aussi : nos libelles de section sont en majuscules espacees, jamais
   en italique. */
optgroup {
  background: var(--bg-card);
  color: var(--text-muted);
  font-style: normal;
  font-weight: 600;
}
/* Les options d'un groupe heritent de la couleur de l'optgroup chez Chromium —
   il faut donc la leur redonner, sinon elles passent en gris de libelle. */
optgroup option {
  color: var(--text-primary);
  font-weight: 400;
}

/* ==================== CHARTS ==================== */
.chart-container {
  position: relative;
  width: 100%;
  max-width: 100%;
  height: 280px;
}

.chart-container-sm { height: 200px; }
.chart-container-lg { height: 380px; }

/* ==================== PROGRESS BARS ==================== */
.progress-bar {
  height: 6px;
  background: var(--bg-input);
  border-radius: 3px;
  overflow: hidden;
}

.progress-bar-fill {
  height: 100%;
  border-radius: 3px;
  transition: width 0.5s ease;
}

/* ==================== LOADING ==================== */
.loading-overlay {
  position: fixed;
  inset: 0;
  z-index: 9998;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-primary);
  backdrop-filter: blur(4px);
}
/* Scoped variant — covers only the page content area (below the titlebar
   and quick-toolbar, right of the sidebar) with a semi-transparent bg.
   Keeps navigation visible and interactive during data reloads so the
   whole UI doesn't blink on every refresh / page switch. */
.loading-overlay.scoped {
  inset: auto;
  top: calc(var(--titlebar-height) + 48px);
  left: var(--sidebar-width);
  right: 0;
  bottom: 0;
  background: color-mix(in srgb, var(--bg-primary) 62%, transparent);
}
/* When a pinned flight strip is visible it sits just below the titlebar;
   push the overlay down a bit more so it doesn't cover that strip either. */
body:has(.pinned-flight-strip) .loading-overlay.scoped {
  top: calc(var(--titlebar-height) + 48px + 56px);
}

.loader {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  animation: loaderFadeIn 0.4s ease-out;
}

.loader-icon {
  width: 64px;
  height: 64px;
  border-radius: 14px;
  box-shadow: var(--shadow-glow-blue);
}

/* Signature loader — a side-view aircraft taxiing along a dashed runway strip */
.loader-spinner {
  position: relative;
  width: 160px;
  height: 32px;
  border: none;
  border-radius: 0;
  background: none;
  animation: none;
  overflow: hidden;
}
.loader-spinner::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 0; right: 0;
  height: 1.5px;
  transform: translateY(-50%);
  background: repeating-linear-gradient(to right, var(--border-medium) 0 8px, transparent 8px 16px);
  opacity: 0.7;
}
.loader-spinner::after {
  content: '';
  position: absolute;
  top: calc(50% - 12px);
  left: 0;
  width: 48px;
  height: 24px;
  background-color: var(--accent-blue);
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 24'><path d='M 45 12 L 41 11 L 28 11 L 13 4 L 10 4 L 22 11 L 11 11 L 6 11 L 2 6 L 1 6 L 4 11 L 3 12 L 4 13 L 1 18 L 2 18 L 6 13 L 11 13 L 22 13 L 10 20 L 13 20 L 28 13 L 41 13 Z'/></svg>");
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 24'><path d='M 45 12 L 41 11 L 28 11 L 13 4 L 10 4 L 22 11 L 11 11 L 6 11 L 2 6 L 1 6 L 4 11 L 3 12 L 4 13 L 1 18 L 2 18 L 6 13 L 11 13 L 22 13 L 10 20 L 13 20 L 28 13 L 41 13 Z'/></svg>");
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  animation: taxi 2.2s cubic-bezier(0.25, 0.1, 0.25, 1) infinite;
}
@keyframes taxi {
  0%   { transform: translateX(-52px); opacity: 0; }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translateX(164px); opacity: 0; }
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Small circular spinner.
 *
 * Four call sites already ASK for this class — my-contracts, my-demands and
 * trading-hall (twice) all render `class="spinner"` — and it was never written.
 * The element drew a box carrying an inline `border-width` with no border
 * STYLE, no colour and no animation: nothing at all, on the four places that
 * tell the player something is loading (revue, passe 7).
 *
 * ⚠️ Deliberately sets NO width, height or border-width: every call site passes
 * those inline (16px / 2px today), and baking them here would silently override
 * nothing and mislead the next caller. `border-style` alone still renders at the
 * CSS default width, so a site that forgets them degrades to a visible spinner
 * rather than to nothing — which is the failure this rule exists to end.
 *
 * `.qsc-spinner` above is the same idea hard-wired to one component (10px,
 * green); this is the theme-aware, size-agnostic version. */
.spinner {
  display: inline-block;
  border-style: solid;
  border-color: var(--border-medium);
  border-top-color: var(--accent-blue);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

/* Indeterminate progress bar — a sliding stripe used when we have no real
   percentage to show (e.g. the cargo/charter type download on the boot gate,
   where two parallel zips make a combined % impractical). Conveys activity
   without a misleading 100%. */
/* translateX of the bar's OWN width (40 % of the track): -100 % is left:-40 %, 250 % is
   left:100 %. Animating `left` re-ran layout every frame (measured 12 % of the main
   thread, 2026-10-06); a transform is composited. */
@keyframes oa-indeterminate {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
.oa-indeterminate-track {
  position: relative;
  width: 100%;
  height: 3px;
  background: var(--border-subtle);
  border-radius: 2px;
  overflow: hidden;
}
.oa-indeterminate-track::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 40%;
  background: var(--accent-cyan);
  border-radius: 2px;
  animation: oa-indeterminate 1.1s ease-in-out infinite;
}

@keyframes shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

@keyframes loaderFadeIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

.loader-text {
  font-size: 13px;
  color: var(--text-secondary);
  font-family: 'IBM Plex Mono', monospace;
  letter-spacing: 0.5px;
}
/* Search-criteria recall shown inside the Recommended Jobs loader. */
.loader-criteria {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin-top: 2px;
  text-align: center;
}
.loader-criteria-types {
  font-size: 12px;
  color: var(--text-secondary);
}
.loader-criteria-metric {
  font-size: 11px;
  color: var(--text-muted);
  font-family: 'IBM Plex Mono', monospace;
}
.loader-steps {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 200px;
}
.loader-step {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-family: 'IBM Plex Mono', monospace;
  padding: 2px 0;
  color: var(--text-muted);
  transition: color 0.2s;
}
.loader-step-done { color: var(--accent-green); }
.loader-step-error { color: var(--accent-red); }
.loader-step-loading { color: var(--text-secondary); }
.loader-step-icon { width: 14px; text-align: center; font-size: 12px; }
.loader-step-loading .loader-step-icon { animation: pulse 1s ease-in-out infinite; }
.loader-step-label { flex: 1; }
.loader-step-ms { font-size: 10px; opacity: 0.7; }
@keyframes pulse { 0%,100% { opacity: 0.3; } 50% { opacity: 1; } }

/* Recommended Jobs "searching & comparing" loader (job-search-loader.jsx) — a
   magnifier scanning a list of jobs, with the best match highlighted. NOT phone
   calls (the jobs already exist; this compares them). Theme-aware via CSS vars. */
.job-search-loader { color: var(--text-muted); }
.job-search-loader svg { display: block; }
.job-search-loader .jsl-rows { stroke: currentColor; opacity: 0.45; }
.job-search-loader .jsl-best { stroke: var(--accent-green); animation: jsl-bestpulse 2.6s ease-in-out infinite; }
.job-search-loader .jsl-scan { stroke: var(--accent-blue); transform-box: view-box; animation: jsl-scan 2.6s ease-in-out infinite; }
@keyframes jsl-scan { 0% { transform: translateY(0); } 50% { transform: translateY(60px); } 100% { transform: translateY(0); } }
@keyframes jsl-bestpulse { 0%, 45% { opacity: 0.15; } 62%, 100% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .job-search-loader .jsl-scan,
  .job-search-loader .jsl-best { animation: none; }
  .job-search-loader .jsl-best { opacity: 1; }
}

/* ==================== MODAL ==================== */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(4px);
}
.modal-content {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  max-width: 640px;
  width: 90%;
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: var(--shadow-elevated);
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-subtle);
  position: sticky;
  top: 0;
  background: var(--bg-card);
  z-index: 1;
}
.modal-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-primary);
}
.modal-close {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 22px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  line-height: 1;
}
.modal-close:hover {
  background: var(--bg-elevated);
  color: var(--text-primary);
}
/* Pied de modale collant — le pendant de .modal-header, qui l'est deja.
   Sans lui, une modale plus haute que 80vh fait defiler ses boutons hors de
   l'ecran, et rien ne dit au lecteur qu'ils existent encore plus bas. Le fond
   reprend celui de .modal-content pour que le contenu passe DERRIERE plutot que
   de transparaitre au travers. */
.modal-footer-sticky {
  position: sticky;
  bottom: 0;
  z-index: 2;
  background: var(--bg-card);
}
.modal-body {
  padding: 16px 20px;
}

/* ==================== TOASTS ==================== */
.toast-container {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 10000;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.toast {
  padding: 12px 14px 12px 20px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 500;
  color: white;
  /* Only entry animation — exit is handled by JS removing the element when its
     duration expires, or on × / hover-pause (see ToastHost in components/toast.jsx). */
  animation: toastIn 0.3s ease;
  max-width: 420px;
  box-shadow: var(--shadow-elevated);
  line-height: 1.45;
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.toast-msg {
  white-space: pre-line;      /* preserve \n in multi-line summaries */
  flex: 1;
  min-width: 0;
}
/* Clickable toast message (opts.onClick) — looks like a link, inherits the
   toast's text color, runs the action then dismisses. */
.toast-msg-action {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Dismiss (×) button — lets the user close a toast early; also the only way
   out for sticky toasts (e.g. the SimBrief fuel import summary). */
.toast-close {
  flex: none;
  margin: -2px -4px 0 0;
  padding: 0 4px;
  background: none;
  border: none;
  color: inherit;
  font-size: 18px;
  line-height: 1;
  font-weight: 700;
  opacity: 0.7;
  cursor: pointer;
}
.toast-close:hover { opacity: 1; }

.toast-success { background: var(--accent-green); }
.toast-error { background: var(--accent-red); }
.toast-warning { background: #f59e0b; color: #000; }
.toast-info { background: var(--accent-blue); }

@keyframes toastIn {
  from { opacity: 0; transform: translateX(100%); }
  to { opacity: 1; transform: translateX(0); }
}

/* Airport loading popup */
.airport-popup {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10000;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-color);
  border-radius: 10px;
  padding: 12px 18px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.45);
  animation: airportPopupIn 0.35s ease-out;
  max-width: 520px;
  min-width: 280px;
}
.airport-popup.done {
  animation: airportPopupIn 0.35s ease-out;
}
.airport-popup svg {
  color: var(--accent-blue);
  margin-top: 1px;
}
.airport-popup-content {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
  color: var(--text-primary);
}
/* TAF disclosure inside map popups (renderTafHTML in taf-service.js).
   Popups are built as HTML strings, so the disclosure is a native <details>
   rather than the wired button the Preact <TafDisplay> uses. These rules give
   it the same +/- affordance and suppress the browser's default triangle,
   which no amount of inline style can remove on its own. */
.taf-popup > summary { list-style: none; }
.taf-popup > summary::-webkit-details-marker { display: none; }
.taf-popup > summary::marker { content: ''; }
.taf-popup > summary::before { content: '+'; font-size: 11px; line-height: 1; }
.taf-popup[open] > summary::before { content: '\2212'; }
/* Show/Hide wording follows the open state (both labels are in the markup). */
.taf-popup > summary .taf-lbl-hide { display: none; }
.taf-popup[open] > summary .taf-lbl-show { display: none; }
.taf-popup[open] > summary .taf-lbl-hide { display: inline; }

.airport-popup-bar {
  width: 100%;
  height: 4px;
  background: var(--bg-tertiary);
  border-radius: 2px;
  overflow: hidden;
}
.airport-popup-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--accent-blue), var(--accent-cyan));
  border-radius: 2px;
  transition: width 0.4s ease;
}
.airport-popup-stats {
  font-size: 11px;
  color: var(--text-muted);
}
@keyframes airportPopupIn {
  from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
@keyframes airportPopupOut {
  from { opacity: 1; transform: translateX(-50%) translateY(0); }
  to   { opacity: 0; transform: translateX(-50%) translateY(-20px); }
}

@keyframes toastOut {
  from { opacity: 1; }
  to { opacity: 0; transform: translateY(20px); }
}

/* ==================== EMPTY STATE ==================== */
.empty-state {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-muted);
}

.empty-state-icon {
  font-size: 48px;
  margin-bottom: 16px;
  opacity: 0.5;
}

.empty-state-title {
  font-size: 18px;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

.empty-state-text {
  font-size: 14px;
  max-width: 400px;
  margin: 0 auto;
  line-height: 1.6;
}

/* ==================== SETTINGS SCREEN ==================== */
.settings-form {
  max-width: none;
}

.settings-section {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 24px;
  margin-bottom: 20px;
}

.settings-section-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border-subtle);
}

/* ==================== DETAIL PANELS ==================== */
.detail-panel {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  margin-bottom: 24px;
}

.detail-panel-header {
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.detail-panel-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-primary);
  /* Fill the flex header so callers can align sub-content (e.g. a monospace
     airport UUID) to the far right with margin-left:auto inside the title. */
  flex: 1;
  min-width: 0;
}

.detail-panel-body { padding: 20px; }

.detail-row {
  display: flex;
  justify-content: space-between;
  padding: 8px 0;
  border-bottom: 1px solid var(--border-subtle);
}

.detail-row:last-child { border-bottom: none; }

.detail-label {
  font-size: 13px;
  color: var(--text-muted);
}

.detail-value {
  font-size: 13px;
  color: var(--text-primary);
  font-weight: 500;
  font-family: 'IBM Plex Mono', monospace;
}

/* ==================== GAUGE ==================== */
.gauge-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.gauge {
  width: 120px; height: 60px;
  position: relative;
  overflow: hidden;
}

.gauge-label {
  font-size: 12px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

/* ==================== MAIN TAB BAR (top-level tabs) ==================== */
.page-nav-btn {
  width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--border-subtle);
  background: var(--bg-elevated); color: var(--text-secondary); padding: 0;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: opacity 0.15s, background 0.15s; flex-shrink: 0;
}
.page-nav-btn:hover:not(:disabled) { background: var(--bg-input); color: var(--text-primary); }
.page-nav-btn:disabled { opacity: 0.25; cursor: default; }

.main-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 20px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border-primary);
}
.main-tabs-inner::-webkit-scrollbar { display: none; }
.fbo-table-scroll::-webkit-scrollbar:horizontal { display: none; }
.tab-scroll-btn { background: var(--bg-elevated); box-shadow: 0 0 8px var(--bg-primary); }

.main-tab-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 20px;
  border: 1px solid var(--border-primary);
  /* OPAQUE, not transparent — and the value is deliberately `--bg-primary`,
     the page ground, so the button looks EXACTLY as it did while no longer
     being a window.
     `.ambient-map` is a decorative Leaflet backdrop painted app-wide at
     `position:fixed; inset:0; z-index:-1; opacity:0.18`, and `.main-content`
     carries no background of its own so that it shows through. A transparent
     tab button therefore let the map's coastlines and roads run underneath its
     own label: ticket #10646 read it as "ghost text behind the Legs and Map
     buttons". The ACTIVE tab was never affected — it paints `--accent-blue`
     over the same spot, which is exactly why the reporter saw it on two of the
     three tabs and not the first.
     Why nobody else saw it: the backdrop is centred on the company's home
     field at zoom 6, so what falls behind the tab bar is empty sea for one
     player and a dense road network for the next. It is not reproducible "in
     general" — it is reproducible by company position. */
  background: var(--bg-primary);
  color: var(--text-muted);
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 13px;
  font-weight: 500;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: var(--transition-fast);
}

.main-tab-btn:hover {
  color: var(--text-secondary);
  background: var(--bg-input);
}

.main-tab-btn.active {
  background: var(--accent-blue);
  color: var(--text-on-accent);
  border-color: var(--accent-blue);
  box-shadow: 0 2px 8px rgba(59,130,246,0.25);
}

/* Staff-tools dropdown (Settings tab bar) — collapses the 6 staff-only tabs
   into one trigger. Menu floats over the tab content below (positioned, high
   z-index; the content area is static so it can't paint over it). */
.settings-staff-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 210px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-sm);
  box-shadow: 0 10px 28px rgba(0,0,0,0.4);
  z-index: 1000;
}
.settings-staff-menu-item {
  text-align: left;
  padding: 8px 12px;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 13px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: var(--transition-fast);
}
.settings-staff-menu-item:hover {
  background: var(--bg-input);
  color: var(--text-primary);
}
.settings-staff-menu-item.active {
  background: var(--accent-blue);
  color: var(--text-on-accent);
}

/* ==================== FLIGHT DISPATCH FORM ==================== */
.dispatch-form {
  max-width: 900px;
}

/* Dispatch form + flight-plan map — full-height 2-pane layout (form left, map
   right). The whole page no longer scrolls: the layout fills the area below the
   screen header, the form list scrolls INTERNALLY, and the map fills ALL the
   remaining width + full height (same pattern as the other map pages). */
.dispatch-layout {
  display: flex;
  gap: 20px;
  align-items: stretch;
  /* titlebar 36 + main-content padding 32 + screen-header (~80, incl. its
     margin-bottom) + a small safety margin → no whole-page overflow. */
  height: calc(100vh - 160px);
}
.dispatch-layout > .dispatch-form {
  flex: 0 1 780px;
  min-width: 380px;
  max-width: 900px;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  padding-right: 10px;
}
.dispatch-map-aside {
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
}
.dispatch-map {
  position: relative;
  height: 100%;
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-elevated);
}
.dispatch-map-canvas { position: absolute; inset: 0; }
.dispatch-map-panel { width: 200px; }
.dispatch-map-empty {
  position: absolute; z-index: 500; top: 14px; left: 50%; transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.6); color: var(--text-secondary);
  padding: 6px 12px; border-radius: 6px; font-size: 12px;
  pointer-events: none; max-width: 70%; text-align: center;
}
/* Checkride briefing banner — bottom-center so it never collides with the
   zoom control (top-left) or the Flight Plan filter panel (top-right). */
/* "The SimBrief plan on file is for another leg, so this is a direct line" —
   shown by BOTH route maps (dispatch + tracking). They have always refused a
   stale OFP; until now they refused it in silence, which reads exactly like
   having no OFP at all. Bottom-left: Leaflet owns top-left (zoom control).
   The `-inline` variant drops the absolute positioning for the tracking map,
   whose bottom-left corner is already a flex stack (checkride checklist +
   altitude legend) that this chip joins at the top. */
.map-ofp-notice {
  position: absolute; z-index: 500; bottom: 16px; left: 16px;
  display: flex; align-items: flex-start; gap: 7px;
  max-width: min(320px, 60%);
  background: rgba(12, 16, 28, 0.88); border: 1px solid rgba(217, 119, 6, 0.5);
  border-radius: 8px; padding: 7px 10px;
  color: var(--accent-orange, #f59e0b); font-size: 11px; line-height: 1.45;
  pointer-events: none; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
}
.map-ofp-notice > span { color: var(--text-secondary); }
.map-ofp-notice svg { flex-shrink: 0; margin-top: 1px; }
.map-ofp-notice-inline { position: static; bottom: auto; left: auto; max-width: 100%; }

.dispatch-map-ckr-banner {
  position: absolute; z-index: 500; bottom: 16px; left: 50%; transform: translateX(-50%);
  background: rgba(12, 16, 28, 0.88); border: 1px solid rgba(245, 158, 11, 0.5);
  /* width: max-content — without it, `left: 50%` caps the shrink-to-fit width
     at half the map, defeating the max-width and over-wrapping the text. */
  border-radius: 8px; padding: 8px 12px; width: max-content; max-width: min(560px, 88%);
  pointer-events: none; display: flex; flex-direction: column; gap: 4px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.45); backdrop-filter: blur(6px);
}
.dispatch-map-ckr-title {
  display: flex; align-items: center; gap: 6px;
  color: var(--accent-orange); font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
}
.dispatch-map-ckr-line {
  display: flex; align-items: baseline; gap: 7px;
  color: var(--text-primary); font-size: 11.5px; line-height: 1.45;
}
.dispatch-map-ckr-dot {
  flex: none; width: 7px; height: 7px; border-radius: 50%;
  align-self: flex-start; margin-top: 4px;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
}
/* Training exercise runner — setup column + full-height map (mirrors
   .dispatch-layout): the page fits the viewport, the setup column scrolls
   INTERNALLY and the map fills all remaining width + height, following window
   resizes. Height budget: titlebar 36 + main padding 32 + ScreenHeader ~80 +
   the runner's back/title row ~44 + safety margin. */
.training-runner-layout {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: 14px;
  align-items: stretch;
  height: calc(100vh - 215px);
  min-height: 420px;
}
.training-runner-setup {
  min-height: 0;
  overflow-y: auto;
  padding-right: 2px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.training-runner-map-card {
  height: 100%;
  min-height: 0;
  padding: 0;
  overflow: hidden;
}
.training-runner-map-card > .training-runner-map {
  height: 100%;
  width: 100%;
}
/* Stack the map under the setup column on narrower windows — the page scrolls
   normally there and the map gets a fixed height (same fallback as dispatch). */
@media (max-width: 1100px) {
  .training-runner-layout { display: flex; flex-direction: column; height: auto; }
  .training-runner-setup { overflow-y: visible; }
  .training-runner-map-card { height: 460px; }
}

/* Instructor transcript — running list of spoken instructions on the Training
   runners. Two sizing modes (see instructor-transcript.jsx): `fill` flexes
   into the viewport-bound setup column (engine-out); default clamps to the
   window height on normally-scrolling runners. */
.training-transcript {
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.training-transcript-fill {
  flex: 1 1 auto;
  min-height: 140px;
}
.training-transcript-title {
  flex: none;
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 6px;
}
.training-transcript-list {
  min-height: 0;
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 6px;
  font-size: 12px; line-height: 1.5; color: var(--text-secondary);
}
.training-transcript-fill .training-transcript-list { flex: 1 1 auto; }
.training-transcript:not(.training-transcript-fill) .training-transcript-list {
  max-height: clamp(150px, 30vh, 460px);
}
.training-transcript-row { display: flex; gap: 8px; }
.training-transcript-time {
  flex: none; padding-top: 1px;
  color: var(--text-muted); font-family: 'IBM Plex Mono', monospace; font-size: 10px;
}

/* Checkride fuel note — read-only fuel section explainer (return-to-base,
   free top-up, no adjustment needed). Amber/trophy themed like the map banner. */
.fm-checkride-fuel-note {
  display: flex; align-items: flex-start; gap: 7px; margin-top: 8px;
  padding: 8px 12px; border-radius: var(--radius-sm);
  background: rgba(245, 158, 11, 0.08); border: 1px solid rgba(245, 158, 11, 0.25);
  font-size: 12px; line-height: 1.45; color: var(--text-secondary);
}
.fm-checkride-fuel-note svg { color: var(--accent-orange); flex: none; margin-top: 2px; }
/* Stack the map under the form on narrower windows — the page scrolls normally
   there and the map gets a fixed height. */
@media (max-width: 1200px) {
  .dispatch-layout { flex-direction: column; height: auto; }
  .dispatch-layout > .dispatch-form {
    height: auto; overflow: visible; padding-right: 0; max-width: 100%; flex: none;
  }
  .dispatch-map-aside { width: 100%; flex: none; }
  .dispatch-map { height: 460px; }
}

.dispatch-section {
  background: var(--bg-elevated);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  padding: 20px;
  margin-bottom: 16px;
}

.dispatch-section-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 16px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
}

.dispatch-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.dispatch-row.cols-3 {
  grid-template-columns: 1fr 1fr 1fr;
}

.dispatch-row > :only-child {
  grid-column: 1 / -1;
}

@media (max-width: 700px) {
  .dispatch-row { grid-template-columns: 1fr; }
}

.dispatch-field {
  position: relative;
  margin-bottom: 12px;
}

.dispatch-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 6px;
}

.dispatch-selected {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--bg-input);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  cursor: pointer;
  transition: var(--transition-fast);
}

.dispatch-selected:hover {
  border-color: var(--accent-blue);
}

.dispatch-clear {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 18px;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}

.dispatch-clear:hover { color: var(--accent-red); }

.dispatch-dropdown {
  max-height: 280px;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-elevated);
}

.dispatch-dropdown-item {
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 1px solid var(--border-primary);
  transition: var(--transition-fast);
}

.dispatch-dropdown-item:last-child { border-bottom: none; }
.dispatch-dropdown-item:hover { background: var(--bg-input); }
.dispatch-item-disabled { cursor: not-allowed; }
.dispatch-item-disabled:hover { background: transparent; }

/* Overflow notice pinned to the bottom of a truncated dropdown — the count has
   to stay readable while the player scrolls the window, otherwise it is just
   another row they scroll past. Not selectable (see TruncationNotice). */
.dispatch-dropdown-more {
  position: sticky;
  bottom: 0;
  padding: 6px 12px;
  text-align: center;
  color: var(--text-muted);
  font-size: 11px;
  background: var(--bg-card);
  border-top: 1px solid var(--border-primary);
  cursor: default;
}

.dispatch-dropdown-empty {
  padding: 12px;
  text-align: center;
  color: var(--text-muted);
  font-size: 13px;
}

/* Aircraft item in dropdown */
.dispatch-ac-item {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.dispatch-ac-ident {
  font-family: 'IBM Plex Mono', monospace;
  font-weight: 700;
  color: var(--text-primary);
  font-size: 13px;
  min-width: 70px;
}

.dispatch-ac-type {
  color: var(--text-secondary);
  font-size: 13px;
  flex: 1;
}

.dispatch-ac-meta {
  display: flex;
  gap: 8px;
  font-size: 12px;
  color: var(--text-muted);
}

/* Employee item in dropdown */
.dispatch-emp-item {
  display: flex;
  align-items: center;
  gap: 10px;
}

.dispatch-emp-name {
  font-weight: 500;
  color: var(--text-primary);
  font-size: 13px;
  flex: 1;
}

.dispatch-emp-meta {
  display: flex;
  gap: 8px;
  font-size: 12px;
  color: var(--text-muted);
}

/* Airport item in dropdown */
.dispatch-airport-item {
  display: flex;
  align-items: center;
  gap: 10px;
}

.dispatch-airport-icao {
  font-family: 'IBM Plex Mono', monospace;
  font-weight: 700;
  color: var(--accent-blue);
  font-size: 13px;
  min-width: 50px;
}

.dispatch-size-badge {
  font-size: 10px;
  font-weight: 600;
  padding: 1px 5px;
  border-radius: 3px;
  white-space: nowrap;
  line-height: 1.3;
}

.dispatch-airport-detail {
  color: var(--text-secondary);
  font-size: 12px;
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dispatch-airport-selected {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;
  /* A narrow chip (Company Jobs at 1371 px: the airport field squeezed to its
     260 px min-width) used to let the size badge spill out of this row, over
     the × clear button AND the button next to the chip (#12125, found in
     passing). The row now clips what it cannot hold, and shrinks in a fixed
     order: the name first (flex:1, basis 0), then the badge (ellipsis), never
     the ICAO. */
  overflow: hidden;
}

.dispatch-airport-selected .dispatch-airport-icao { flex-shrink: 0; }

.dispatch-airport-selected .dispatch-size-badge {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dispatch-airport-name {
  color: var(--text-secondary);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}

/* Multi-select chips */
.dispatch-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}

.dispatch-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: 12px;
  background: rgba(59,130,246,0.12);
  border: 1px solid rgba(59,130,246,0.2);
  color: var(--accent-blue);
  font-size: 12px;
  font-weight: 500;
}

.dispatch-chip-x {
  background: none;
  border: none;
  color: var(--accent-blue);
  font-size: 14px;
  cursor: pointer;
  padding: 0 2px;
  line-height: 1;
  opacity: 0.6;
}

.dispatch-chip-x:hover { opacity: 1; color: var(--accent-red); }

/* Hints below inputs */
.dispatch-hint {
  display: block;
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 4px;
}

.dispatch-hint-ok { color: var(--accent-green, #10b981); }
.dispatch-hint-err { color: var(--accent-red, #ef4444); }

/* Info bar */
.dispatch-info-bar {
  margin-top: 8px;
  padding: 8px 12px;
  background: rgba(59,130,246,0.06);
  border: 1px solid rgba(59,130,246,0.12);
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--text-secondary);
}

/* Summary */
.dispatch-summary {
  margin-bottom: 16px;
}

.dispatch-summary-row {
  display: flex;
  justify-content: space-between;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-primary);
  font-size: 13px;
}

.dispatch-summary-row:last-child { border-bottom: none; }

.dispatch-summary-row span {
  color: var(--text-muted);
}

.dispatch-summary-row strong {
  color: var(--text-primary);
}

/* Submit button */
.dispatch-submit {
  width: 100%;
  padding: 12px 24px;
  font-size: 15px;
  font-weight: 600;
  border-radius: var(--radius-sm);
}

.dispatch-submit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.dispatch-submit.loading {
  pointer-events: none;
}

/* ==================== TAB SYSTEM (in-page tabs) ==================== */
.tabs {
  display: flex;
  gap: 2px;
  background: var(--bg-input);
  border-radius: var(--radius-sm);
  padding: 3px;
  margin-bottom: 20px;
}

.tab-btn {
  flex: 1;
  padding: 8px 16px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 13px;
  font-weight: 500;
  border-radius: 4px;
  cursor: pointer;
  transition: var(--transition-fast);
}

.tab-btn:hover { color: var(--text-secondary); }

.tab-btn.active {
  background: var(--bg-elevated);
  color: var(--text-primary);
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}

.tab-content { display: none; }
.tab-content.active { display: block; animation: fadeIn 0.2s ease; }

/* ==================== CONDITION BAR ==================== */
.condition-bar {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 60px;
}

.condition-bar-track {
  width: 100%;
  height: 5px;
  background: var(--bg-input);
  border-radius: 3px;
  overflow: hidden;
}

.condition-bar-fill {
  height: 100%;
  border-radius: 3px;
}

.condition-bar-value {
  font-size: 11px;
  font-family: 'IBM Plex Mono', monospace;
  color: var(--text-secondary);
  text-align: center;
}

/* ==================== ICAO LINK ==================== */
.icao-link {
  color: var(--accent-cyan);
  cursor: pointer;
  /* A code and its favorite marker are ONE token — never split across lines.
     Since 512d638b IcaoLink appends a star (9px icon + 3px margin) to any
     starred airport, and the code text and that star are two separate inline
     boxes: a wrapping container is free to break between them. Measured on
     Company Jobs 2026-08-08, where the Legs cell is 190px and renders a chain:
     9 of the 46 starred codes on screen rendered as "LFST" at the end of one
     line and a lone star at the start of the next, sitting against the "→" and
     the destination — so the marker read as belonging to the WRONG airport.
     nowrap costs no width (unlike reserving a slot on every code) and cannot
     overlap a neighbour (unlike a zero-width marker): containers still wrap
     BETWEEN codes, just never inside one. Verified live on both affected
     pages: 9 splits to 0, with no row growing or shrinking and no container
     overflowing.
     Note this is a rule, not an inline style, so a call-site that genuinely
     needs a break can still override it. */
  white-space: nowrap;
}
.icao-link:hover {
  text-decoration: underline;
}

/* ==================== CONDITIONS TRIPLE CHART ==================== */
.conditions-triple {
  display: flex;
  gap: 4px;
}
.conditions-triple-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.conditions-triple-item .chart-container-sm {
  height: 120px;
}
.conditions-triple-label {
  font-size: 11px;
  color: var(--text-secondary);
  margin-top: 4px;
  text-align: center;
}
.conditions-legend {
  display: flex;
  justify-content: center;
  gap: 12px;
  padding: 8px 0 4px;
  font-size: 11px;
  color: var(--text-secondary);
}
.conditions-legend-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 4px;
  vertical-align: middle;
}

/* ==================== API KEY HELP ==================== */
.btn-icon-sm {
  width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--text-muted);
  background: transparent; color: var(--text-muted); font-size: 11px; font-weight: 700;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  padding: 0; line-height: 1; flex-shrink: 0;
}
.btn-icon-sm:hover { border-color: var(--accent-blue); color: var(--accent-blue); }
.api-key-help {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 12px 14px; margin: 8px 0; font-size: 12px; color: var(--text-secondary); line-height: 1.6;
}
.api-key-help p { margin: 0 0 6px; }
.api-key-help ol { margin: 4px 0 6px; padding-left: 20px; }
.api-key-help li { margin-bottom: 2px; }

/* Fuel slider with dual-color track */
.fuel-slider { -webkit-appearance: none; appearance: none; height: 8px; border-radius: 4px; outline: none; cursor: pointer; }
.fuel-slider::-webkit-slider-runnable-track { height: 8px; border-radius: 4px; background: var(--track-bg, var(--bg-input)); }
.fuel-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: #fff; border: 2px solid #3b82f6; margin-top: -5px; cursor: pointer; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
.fuel-slider::-moz-range-track { height: 8px; border-radius: 4px; background: var(--track-bg, var(--bg-input)); border: none; }
.fuel-slider::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: #fff; border: 2px solid #3b82f6; cursor: pointer; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }

/* Quick-access toolbar */
.quick-toolbar {
  display: flex; align-items: center; gap: 1px; padding: 2px 8px;
  margin: -16px -20px 0; /* counteract main-content padding */
  border-bottom: 1px solid var(--border-subtle); background: var(--bg-card);
  overflow: visible; flex-shrink: 0;
  /* z-index 1200, NOT 100. `position:sticky` + z-index makes this bar its own
     stacking context, which TRAPS the customize dropdown's z-index:10000
     inside it — so the whole bar competes at the root as a single layer worth
     100. Page-level map overlays (logistic-map controls 650/1000/1001, world
     map 1100) outranked that and painted straight over the open dropdown
     (tester Hannes S., 2026-07-30). 1200 clears every in-page overlay while
     staying below app chrome that must cover the toolbar: maintenance pill
     (9600), modals (9999), toasts (10000). */
  position: sticky; top: -16px; z-index: 1200;
}
.quick-toolbar .page-nav-btn { width: 22px; height: 22px; flex-shrink: 0; }
.quick-toolbar-sep { width: 1px; height: 16px; background: var(--border-subtle); margin: 0 4px; flex-shrink: 0; }
.quick-toolbar-btn {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: none; background: none; cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background 0.15s, transform 0.1s; opacity: 0.65;
}
.quick-toolbar-btn:hover { background: var(--bg-input); opacity: 1; transform: scale(1.15); }
.quick-toolbar-btn.active { opacity: 1; background: rgba(59,130,246,0.12); }
.quick-toolbar-edit {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; margin-left: auto; flex-shrink: 0;
  border: 1px dashed var(--border); background: none; color: var(--text-muted);
  border-radius: var(--radius-sm); cursor: pointer; transition: all 0.15s;
}
.quick-toolbar-edit:hover { background: var(--bg-input); color: var(--text-primary); }
.quick-toolbar-edit.active { background: var(--accent-blue); color: var(--text-on-accent); border-color: var(--accent-blue); }

/* Toolbar dropdown editor */
.toolbar-dropdown {
  position: absolute; right: 0; top: 100%; margin-top: 4px;
  /* 260px, was 220px — the reorder rows carry a drag handle and two nudge
     buttons on top of the icon+label, and page names were ellipsising away. */
  width: 260px; max-height: 460px; overflow-y: auto;
  background: var(--bg-card); border: 1px solid var(--border-medium);
  border-radius: var(--radius-sm); box-shadow: 0 8px 24px rgba(0,0,0,0.3);
  padding: 10px; z-index: 10000;
}
.toolbar-dropdown h4 {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--text-muted); margin: 8px 0 4px; padding: 0;
}
.toolbar-dropdown h4:first-child { margin-top: 0; }
.toolbar-dropdown-item {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 6px; border-radius: 4px; cursor: pointer;
  font-size: 12px; color: var(--text-muted);
  transition: background 0.1s;
}
.toolbar-dropdown-item:hover { background: var(--bg-input); }
.toolbar-dropdown-item.included { color: var(--text-primary); }
.toolbar-dropdown-item input[type="checkbox"] { cursor: pointer; margin: 0; }
.toolbar-dropdown-reset {
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border-subtle);
  text-align: center;
}
.toolbar-dropdown-reset button {
  font-size: 11px; color: var(--text-muted); background: none; border: none;
  cursor: pointer; padding: 4px 12px; border-radius: 4px;
}
.toolbar-dropdown-reset button:hover { color: var(--text-primary); background: var(--bg-input); }

/* ==================== LIGHT MODE OVERRIDES ==================== */
[data-mode="light"] .nav-btn:hover { background: rgba(0,0,0,0.04); }
[data-mode="light"] .nav-btn.active { background: rgba(59,130,246,0.08); }
[data-mode="light"] .toast { box-shadow: 0 4px 16px rgba(0,0,0,0.12); }
[data-mode="light"] .tab-btn.active { background: var(--accent); color: var(--text-on-accent); box-shadow: 0 1px 3px rgba(0,0,0,0.12); }
[data-mode="light"][data-theme="ocean"] .nav-btn.active { background: rgba(6,182,212,0.08); }
[data-mode="light"][data-theme="forest"] .nav-btn.active { background: rgba(16,185,129,0.08); }
[data-mode="light"][data-theme="sunset"] .nav-btn.active { background: rgba(245,158,11,0.08); }
[data-mode="light"][data-theme="amethyst"] .nav-btn.active { background: rgba(139,92,246,0.08); }
[data-mode="light"][data-theme="nord"] .nav-btn.active { background: rgba(136,192,208,0.08); }
[data-mode="light"][data-theme="dracula"] .nav-btn.active { background: rgba(189,147,249,0.08); }
[data-mode="light"][data-theme="solarized"] .nav-btn.active { background: rgba(38,139,210,0.08); }
[data-mode="light"][data-theme="monokai"] .nav-btn.active { background: rgba(102,217,239,0.08); }
[data-mode="light"][data-theme="catppuccin"] .nav-btn.active { background: rgba(137,180,250,0.08); }
[data-mode="light"][data-theme="slate"] .nav-btn.active { background: rgba(148,163,184,0.08); }
[data-mode="light"][data-theme="gruvbox"] .nav-btn.active { background: rgba(131,165,152,0.08); }

/* ==================== COMPANY SEARCH ==================== */
.company-search-results {
  position: fixed;
  max-height: 320px;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-elevated);
  z-index: 9999;
}

.company-search-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
  transition: var(--transition-fast);
  border-bottom: 1px solid var(--border-subtle);
}

.company-search-item:last-child { border-bottom: none; }

.company-search-item:hover {
  background: rgba(59,130,246,0.1);
}

.company-search-code {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 13px;
  font-weight: 700;
  color: var(--accent-blue);
  min-width: 50px;
  text-align: center;
  padding: 4px 8px;
  background: rgba(59,130,246,0.1);
  border-radius: 4px;
  letter-spacing: 0.5px;
}

.company-search-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.company-search-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.company-search-meta {
  font-size: 11px;
  color: var(--text-muted);
  font-family: 'IBM Plex Mono', monospace;
}

/* ==================== RESPONSIVE ==================== */
@media (max-width: 1400px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 1000px) {
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .grid-2-1, .grid-1-2 { grid-template-columns: 1fr; }
}

@media (max-width: 800px) {
  .grid-2 { grid-template-columns: 1fr; }
  .grid-4 { grid-template-columns: 1fr; }
  .grid-3 { grid-template-columns: 1fr; }
}

/* ==================== WEB MODE (no Electron titlebar) ==================== */
.web-mode .sidebar {
  top: 0;
}
.web-mode .main-content {
  top: 0;
}


/* Map card container */
.map-page {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}
.map-card {
  padding: 0;
  overflow: hidden;
  flex: 1;
  min-height: 0;
  position: relative;
}

/* Map markers */
.map-marker-aircraft, .map-marker-employee, .map-marker-airport, .factory-loc-pin { background: none; border: none; }
.map-tooltip { font-family: 'IBM Plex Mono', monospace; font-size: 11px; }

/* Map filter panel */
.map-filter-panel {
  /* z-index 650 sits above leaflet marker-pane (600) but below popup-pane
     (700) so airport popups always render on top of the filter panel. */
  position: absolute; z-index: 650; top: 10px; right: 10px;
  background: rgba(20, 22, 28, 0.92); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 14px; min-width: 170px; backdrop-filter: blur(8px);
  /* Same `auto` half as .map-overlay-panel — see the note there. */
  pointer-events: auto;
  transition: min-width 0.25s ease, padding 0.25s ease;
  max-height: calc(100% - 60px); overflow-y: auto;
}
.map-filter-panel.map-filter-collapsed {
  min-width: unset; padding: 0; overflow: hidden;
}
/* Bottom anchor — used by the flight-tracking map, whose top-right corner is
   already occupied by the live info pill and the layer-toggle rail. Same
   panel, same collapse behaviour, just the other end of the map. The
   bottom-LEFT corner is the altitude legend + checkride checklist, so the two
   stacks never meet. */
/* ⚠️ bottom: 28px, not 10px. Leaflet's tile attribution sits flush at the
   bottom-right of the map and is 16.8 px tall (measured); at 10 px the panel's
   lower 6.8 px slid UNDER it — 20 % of the collapsed "APT" button's height,
   where the click landed on the attribution link instead. The attribution wins
   because `.leaflet-control` is z-index 800 against this panel's 650 and
   re-enables pointer-events despite the `none` on its corner. Raising the
   z-index instead would hide the attribution, which the OSM / Esri tile
   licences require — so the panel moves up, exactly as the Company Jobs legend
   had to (logistic-map.jsx, bottom:20px for the same reason). */
/* ⚠️ right: 48px, not 10px, and a height cap. The layer rail (12 buttons of
   28 px, 4 px gaps, one 6 px divider ≈ 386 px) hangs under the info pill down
   the same right edge, and on a 690 px map — the Map tab in a 1000 px window —
   its bottom sits ~530 px down while the open panel (~300 px) started at
   ~360 px: the lower half of the rail was drawn over the size scale
   (2026-09-07). 48 = 10 px margin + 28 px rail + 10 px gap, so the panel now
   sits beside the rail, in both states (a collapsed "APT" left at right:10px
   would meet the rail's last button on any map under ~600 px). The cap keeps
   it out of the band the pill occupies: the pill is at most 9 lines of
   17.6 px + 9 px target divider + 18 px padding/border ≈ 185 px from
   top:10px, so 218 = 10 + 190 + 18 of breathing room; past that the panel
   scrolls (the class's own overflow-y:auto) instead of climbing behind the
   pill, which draws above it (z-index 1000 vs 650). */
.map-filter-panel.map-filter-panel-bottom {
  top: auto; bottom: 28px; right: 48px; max-height: calc(100% - 218px);
}
.map-filter-toggle {
  display: flex; align-items: center; gap: 6px; cursor: pointer;
  color: #ccc; padding: 6px 8px; user-select: none;
}
.map-filter-toggle:hover { color: #fff; }
.map-filter-collapsed .map-filter-toggle {
  padding: 8px 10px;
}
.map-filter-title { font-size: 11px; font-weight: 600; color: #ccc; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }
.map-filter-item { display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 3px 0; font-size: 12px; color: #ddd; }
.map-filter-item:hover { color: #fff; }
.map-filter-item input[type="checkbox"] { width: 14px; height: 14px; accent-color: var(--accent-blue); cursor: pointer; flex-shrink: 0; }
.map-filter-disabled { opacity: 0.4; cursor: not-allowed; }
.map-filter-disabled input[type="checkbox"] { cursor: not-allowed; }
.map-filter-dot { border-radius: 50%; flex-shrink: 0; }
.map-filter-label { white-space: nowrap; }
.map-filter-sep { height: 1px; background: var(--border); margin: 6px 0; }

/* ── Airport ground-layout mini-map (components/airport-layout-map.jsx) ── */
.airport-layout-map-wrap { position: relative; width: 100%; border-radius: 8px; overflow: hidden; border: 1px solid var(--border); }
.airport-layout-map { position: absolute; inset: 0; background: #0b0d12; }
/* Narrower than the world-map panel — it shares a modal with other controls. */
.airport-layout-panel { min-width: 132px; padding: 8px 10px; top: 8px; right: 8px; }
/* Centred message while the layout loads, or when there is none to show. */
.airport-layout-overlay {
  position: absolute; z-index: 640; inset: 0; display: flex; align-items: center; justify-content: center;
  padding: 0 24px; text-align: center; font-size: 12px; color: var(--text-muted);
  background: rgba(11, 13, 18, 0.72); pointer-events: none;
}
/* Badge for a layout served from cache without a sim to confirm it is current. */
.airport-layout-wind {
  position: absolute; z-index: 650; left: 8px; bottom: 8px;
  display: flex; align-items: center; gap: 2px; line-height: 0;
  /* Never swallows a click meant for a stand or a runway dot underneath. */
  pointer-events: none;
  /* A dark halo so the staff reads over pale apron as well as over water —
     the symbol carries no background plate of its own, because the request was
     the wind SYMBOL and nothing else. */
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.9)) drop-shadow(0 0 5px rgba(0, 0, 0, 0.6));
}
/* The figures beside the barb — deliberately the SAME colour as the staff, so
   the pair reads as one indicator rather than as a symbol with a caption. */
.airport-layout-wind-symbol { width: 64px; height: 64px; }
.airport-layout-wind-text {
  color: #e2e8f0; font-family: 'IBM Plex Mono', monospace; font-size: 12px;
  font-weight: 600; line-height: 1; white-space: nowrap;
}
.airport-layout-stale {
  position: absolute; z-index: 650; left: 82px; bottom: 8px;
  background: rgba(245, 158, 11, 0.14); border: 1px solid rgba(245, 158, 11, 0.35);
  color: #f59e0b; font-size: 11px; padding: 3px 8px; border-radius: 6px;
}
/* Disk-usage bar for the airport-layout cache (Settings → Flight Sim). */
.layout-cache-bar {
  height: 6px; border-radius: 3px; background: rgba(255,255,255,0.08); overflow: hidden;
}
.layout-cache-bar-fill {
  height: 100%; background: var(--accent-blue); border-radius: 3px; transition: width 0.25s ease;
}
[data-mode="light"] .layout-cache-bar { background: rgba(0,0,0,0.08); }

/* Empty state for a map that is ABOUT one airport with no layout captured yet.
   Bottom-left, clear of Leaflet's zoom control (top-left) and attribution
   (bottom-right). Interactive, so it must sit above the panes and take pointer
   events — unlike the purely informative badges around it. */
.airport-layout-empty {
  position: absolute; z-index: 660; left: 10px; bottom: 10px; max-width: min(70%, 360px);
  background: rgba(20, 22, 28, 0.94); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; backdrop-filter: blur(8px);
}
.airport-layout-empty-title {
  font-size: 12px; font-weight: 700; color: var(--text-primary); margin-bottom: 4px;
}
.airport-layout-empty-body {
  font-size: 11px; line-height: 1.5; color: var(--text-muted); margin-bottom: 9px;
}
.airport-layout-empty-btn { font-size: 12px; padding: 5px 12px; }
/* Compact drops the explanatory paragraph, which was carrying the gap under the
   title — put it back so the button never sits flush against the heading. */
.airport-layout-empty-compact .airport-layout-empty-title { margin-bottom: 9px; }
/* Bottom-CENTRE variant, for the world map. Bottom-left is taken by the VATSIM
   stats panel when that layer is on, top-left by the zoom control and top-right
   by the filter panel — the centre is the one reliably free spot, and it also
   happens to sit under the airport this prompt is about. */
.airport-layout-empty-center { left: 50%; right: auto; transform: translateX(-50%); text-align: center; }
.airport-layout-empty-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.airport-layout-empty-hint {
  font-size: 10.5px; line-height: 1.45; color: var(--text-muted); margin-top: 6px;
}
.airport-layout-empty-error {
  font-size: 10.5px; line-height: 1.45; color: var(--accent-red); margin-top: 6px;
}
[data-mode="light"] .airport-layout-empty { background: rgba(255,255,255,0.94); border-color: rgba(0,0,0,0.12); }

/* Taxiway names and runway designators — divIcon, so styled here not inline. */
.airport-layout-label {
  font-size: 10px; font-weight: 700; color: #e2e8f0; white-space: nowrap;
  text-shadow: 0 0 3px #000, 0 0 2px #000; pointer-events: none;
}
[data-mode="light"] .airport-layout-map { background: #eef1f5; }
[data-mode="light"] .airport-layout-overlay { background: rgba(255, 255, 255, 0.78); }
[data-mode="light"] .airport-layout-label { color: #1e293b; text-shadow: 0 0 3px #fff, 0 0 2px #fff; }

/* Airport-size slider — vertical cumulative threshold (largest at top) */
.map-size-slider-row { display: flex; align-items: stretch; gap: 10px; margin-bottom: 8px; }
.map-size-slider {
  writing-mode: vertical-lr;     /* vertical orientation (Chromium 140) */
  /* thumb at the bottom = full pull = all sizes shown (smallest threshold) */
  width: 18px; margin: 0; padding: 0; align-self: stretch;
  accent-color: var(--accent-blue); cursor: pointer;
}
.map-size-scale { display: flex; flex-direction: column; justify-content: space-between; flex: 1; gap: 2px; }
.map-size-scale-item { display: flex; align-items: center; gap: 8px; padding: 1px 0; font-size: 12px; color: #ddd; cursor: pointer; transition: opacity 0.15s ease; }
.map-size-scale-item:hover { color: #fff; }
.map-size-scale-item .map-filter-label { white-space: nowrap; }
.map-size-inactive { opacity: 0.4; }
.map-filter-dot-none {
  width: 10px; height: 10px;
  background: transparent;
  border: 1px dashed currentColor;
  border-radius: 50%;
  opacity: 0.7;
}
[data-mode="light"] .map-size-scale-item { color: #333; }
[data-mode="light"] .map-size-scale-item:hover { color: #000; }

/* Map loading overlay */
.map-loading-overlay {
  position: absolute; z-index: 1001; top: 50%; left: 50%;
  transform: translate(-50%, -50%); pointer-events: none;
}
.map-loading-box {
  background: rgba(20, 22, 28, 0.95); border: 1px solid var(--border);
  border-radius: 10px; padding: 16px 20px; min-width: 200px;
  backdrop-filter: blur(10px); color: #ddd;
}
.map-loading-step {
  display: flex; align-items: center; gap: 8px; font-size: 12px;
  padding: 3px 0;
}
.map-loading-icon {
  width: 16px; text-align: center; font-weight: 700; flex-shrink: 0;
}
.map-loading-icon.loading { color: var(--accent-blue); animation: map-spin 1s linear infinite; display: inline-block; }
.map-loading-icon.done { color: var(--accent-green); }
.map-loading-icon.error { color: var(--accent-red, #ef4444); }
@keyframes map-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* Map cursors: grab on map background, pointer on markers */
.leaflet-container { cursor: grab; cursor: -webkit-grab; }
.leaflet-dragging .leaflet-container { cursor: grabbing; cursor: -webkit-grabbing; }
.leaflet-marker-icon { cursor: pointer !important; }
.map-marker-allflights { cursor: pointer !important; }
.map-marker-allflights:hover { transform: scale(1.4); z-index: 10000 !important; filter: brightness(1.3); transition: transform 0.15s, filter 0.15s; }

/* Map cluster icons */
.map-cluster-icon { background: none !important; border: none !important; }
.leaflet-marker-icon.map-cluster-icon div { box-shadow: 0 0 8px rgba(0,0,0,0.6); }

/* Map ICAO permanent label */
.map-icao-label {
  background: rgba(0,0,0,0.7) !important; border: none !important; box-shadow: none !important;
  color: #fff !important; font-size: 12px; font-weight: 700; font-family: 'IBM Plex Mono', monospace;
  text-shadow: 0 0 3px #000, 0 0 6px #000;
  padding: 1px 4px !important; border-radius: 3px; white-space: nowrap;
}
.map-icao-label.map-icao-day {
  background: rgba(0,0,0,0.7) !important;
  color: #fff !important;
  text-shadow: none;
}
.map-icao-label::before { display: none !important; }
.map-icao-label.leaflet-tooltip-top::before,
.map-icao-label.leaflet-tooltip-bottom::before,
.map-icao-label.leaflet-tooltip-left::before,
.map-icao-label.leaflet-tooltip-right::before { display: none !important; }
/* Prevent ICAO labels from sliding during Leaflet zoom animation */
.leaflet-zoom-anim .map-icao-label { transition: none !important; }

/* Boost OpenAIP tiles visibility on dark background */
.leaflet-openaip-layer img { filter: brightness(2.0) saturate(1.8) contrast(1.3); }

/* METAR station model markers — no default leaflet divIcon bg */
.metar-station-icon { background: none !important; border: none !important; }

/* Sightseeing POI pin — mirror WPF POIPushPinTemplate (App.xaml:1331-1370):
   black disc with a camera glyph centered, white outline for contrast over
   any tile theme. Container is 28×28 (matches L.divIcon iconSize). */
.poi-pin {
  width: 28px; height: 28px; border-radius: 50%;
  background: #0a0a0a; border: 2px solid #fff;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 6px rgba(0,0,0,0.6);
}

/* Map airport popup */
.map-airport-popup .leaflet-popup-content-wrapper {
  background: var(--bg-card); color: var(--text-primary);
  border-radius: var(--radius-sm); border: 1px solid var(--border);
}
.map-airport-popup .leaflet-popup-tip { background: var(--bg-card); }
/* FBO hover summary (Operations Map, #10651) — same card as the click popup
   above, so hover and click read as one surface. Leaflet's default tooltip is
   white with dark text, which the popup's light-on-dark colours cannot sit on. */
.leaflet-tooltip.map-fbo-tooltip {
  background: var(--bg-card); color: var(--text-primary);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 8px 12px; white-space: normal;
}
.leaflet-tooltip-top.map-fbo-tooltip::before { border-top-color: var(--border); }
.airport-popup-content { font-size: 13px; line-height: 1.5; }
.airport-popup-btn {
  display: block; width: 100%; margin-top: 8px; padding: 5px 0; border: none;
  background: var(--accent-blue); color: var(--text-on-accent); border-radius: var(--radius-sm);
  font-size: 12px; font-weight: 600; cursor: pointer; text-align: center;
}
.airport-popup-btn:hover { opacity: 0.85; }
.airport-popup-navigraph-btn {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; margin-top: 6px; padding: 6px 10px;
  background: #00c2cb; color: #fff; border: 1px solid #009ba3;
  border-radius: var(--radius-sm); font-size: 11px; font-weight: 600;
  cursor: pointer; text-align: center;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.airport-popup-navigraph-btn:hover { background: #00a8b0; border-color: #007f86; }
.airport-popup-navigraph-btn svg { flex-shrink: 0; fill: #fff; }
.btn.btn-navigraph {
  background: #00c2cb; color: #fff; border-color: #009ba3;
  display: inline-flex; align-items: center; gap: 5px;
}
.btn.btn-navigraph:hover { background: #00a8b0; border-color: #007f86; color: #fff; }
.btn.btn-navigraph svg { flex-shrink: 0; fill: #fff; }

/* Per-leg charts links — the dispatch info bar and the tracking route strip.  */
/* Small SOLID pills rather than the translucent style of their VATSIM         */
/* neighbour: #00c2cb is the Navigraph brand colour, hardcoded here as it      */
/* already is three times above, because it must not shift with the theme.     */
/* A cyan TEXT on a 12%-alpha ground would have washed out in light mode —     */
/* MEASURED 2026-09-10 over CDP, [data-mode="light"] puts --bg-primary at      */
/* #faf6ed. White on the solid brand ground was captured on ramp dark,         */
/* solarized dark and light mode and reads the same on all three.              */
.ng-charts { display: inline-flex; align-items: center; gap: 5px; }
.ng-charts-label {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.3px; color: #00c2cb;
}
.ng-charts-label svg { flex-shrink: 0; fill: #00c2cb; }
.ng-charts-pill {
  padding: 1px 7px; border: 1px solid #009ba3; border-radius: 3px;
  background: #00c2cb; color: #fff;
  font-family: 'IBM Plex Mono', monospace; font-size: 10px; font-weight: 700;
  letter-spacing: 0.5px; cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.ng-charts-pill:hover { background: #00a8b0; border-color: #007f86; }

/* Airport-popup mission rows — clickable to highlight that mission's route */
/* on the map (route gets bringToFront + lc-route-pulse animation). */
.lc-popup-row {
  transition: background 0.15s ease;
}
.lc-popup-row:hover {
  background: rgba(255, 255, 255, 0.05);
}
.lc-popup-row.lc-popup-row-selected {
  background: rgba(255, 255, 255, 0.10);
}
.lc-popup-row:hover .lc-popup-row-target,
.lc-popup-row.lc-popup-row-selected .lc-popup-row-target {
  opacity: 1;
}

/* Pulsing animation for the highlighted mission's route polyline.   */
/* Applied via L.polyline({ className: 'lc-route-pulse' }) — targets */
/* the underlying SVG <path stroke-opacity>.                         */
@keyframes lc-route-pulse {
  0%, 100% { stroke-opacity: 1; }
  50%      { stroke-opacity: 0.35; }
}
.lc-route-pulse {
  animation: lc-route-pulse 1.4s ease-in-out infinite;
}

/* Mission detail modal — per-leg selectable rows. Clicking one makes */
/* that single leg pulse on the map (the other legs go quiet).        */
.lc-modal-leg-row {
  transition: background 0.15s ease;
}
.lc-modal-leg-row:hover {
  background: rgba(255, 255, 255, 0.04);
}
.lc-modal-leg-row.lc-modal-leg-row-selected {
  background: rgba(255, 255, 255, 0.08);
}
.lc-modal-leg-row:hover .lc-modal-leg-target,
.lc-modal-leg-row.lc-modal-leg-row-selected .lc-modal-leg-target {
  opacity: 1 !important;
}

/* Logistic-center / Freelance-charters airport popup (className: 'lc-dark-popup'). */
/* The default Leaflet close button is a low-contrast × that disappears against */
/* the dark popup bg. Wrap it in a subtle rounded chip so the user can find it. */
/*                                                                              */
/* Specificity note: `.leaflet-container a.leaflet-popup-close-button` (in      */
/* leaflet.css) sets `position: absolute; top: 0; right: 0`. A dragged popup   */
/* leaves its map — since #10657 into a `leaflet-container` host (see           */
/* makePopupDraggable), so that selector keeps matching for EVERY popup class.  */
/* Before, the wrapper went to `document.body` bare and this rule was the only  */
/* thing holding a close button in place, on this class only. It must still    */
/* out-rank Leaflet's selector to keep the chip's own top/right/size.           */
.lc-dark-popup.leaflet-popup a.leaflet-popup-close-button,
.lc-dark-popup a.leaflet-popup-close-button {
  position: absolute;
  top: 8px;
  right: 8px;
  background: rgba(148, 163, 184, 0.18);
  color: #e2e8f0;
  border-radius: 50%;
  width: 22px;
  height: 22px;
  line-height: 18px;
  text-align: center;
  font-size: 18px;
  font-weight: 700;
  text-decoration: none;
  padding: 0;
  transition: background 0.15s ease, color 0.15s ease;
}
.lc-dark-popup.leaflet-popup a.leaflet-popup-close-button:hover,
.lc-dark-popup a.leaflet-popup-close-button:hover {
  background: rgba(148, 163, 184, 0.35);
  color: #ffffff;
  text-decoration: none;
}

/* Map search */
.map-search-panel {
  position: absolute; z-index: 1001; top: 10px; left: 50px;
  width: 320px;
}
.map-search-input {
  width: 100%; box-sizing: border-box;
  background: rgba(20, 22, 28, 0.92); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 12px; color: #fff; font-size: 13px; font-family: inherit;
  backdrop-filter: blur(8px); outline: none;
}
.map-search-input::placeholder { color: #888; }
.map-search-input:focus { border-color: var(--accent-blue); }
.map-search-results {
  background: rgba(20, 22, 28, 0.95); border: 1px solid var(--border); border-top: none;
  border-radius: 0 0 8px 8px; max-height: 280px; overflow-y: auto;
  backdrop-filter: blur(8px);
}
.map-search-item {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 12px; cursor: pointer; font-size: 12px; color: #ccc;
}
.map-search-item:hover { background: rgba(255,255,255,0.08); color: #fff; }
.map-search-icao { font-weight: 600; color: var(--accent-blue); min-width: 50px; font-family: 'IBM Plex Mono', monospace; }
.map-search-detail { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.map-search-mil { font-size: 10px; font-weight: 600; color: #2ecc71; background: rgba(46,204,113,0.15); padding: 1px 5px; border-radius: 3px; }

/* Shared overlay panel for mini-maps (pending-jobs, etc.) */
.map-overlay-panel {
  background: rgba(15,23,42,0.92); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm); backdrop-filter: blur(8px);
  /* Overlay panels are the `auto` half of the map-overlay rule: their wrapper
     carries `pointer-events:none` so it cannot swallow the map, and the panel
     — which is opaque, so blocking under it is legitimate — takes its events
     back. Declared on the class rather than at each call site so a wrapper can
     be made transparent to the mouse without hunting down its children.
     No-op wherever no wrapper sets `none`. */
  pointer-events: auto;
}

.map-traffic-loading {
  position: absolute; z-index: 1100; top: 50%; left: 50%;
  transform: translate(-50%,-50%); background: rgba(0,0,0,0.98);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  padding: 20px 32px; display: flex; align-items: center; gap: 12px;
  pointer-events: none; color: #fff;
}

.map-airport-count {
  position: absolute; z-index: 1000; bottom: 8px; left: 8px;
  background: rgba(0,0,0,0.7); color: #ddd;
  padding: 4px 10px; border-radius: 4px; font-size: 12px; pointer-events: none;
}

/* "No airport size selected" recovery pill — sits just above the count badge.
   Clickable on purpose (the count next to it is not): it is the way OUT of an
   empty map, not a label. See the map's minSize >= 6 branch. */
.map-no-size-hint {
  position: absolute; z-index: 1000; bottom: 36px; left: 8px;
  background: rgba(234, 179, 8, 0.92); color: #1a1a1a;
  border: none; padding: 4px 10px; border-radius: 4px;
  font-size: 12px; font-weight: 600; cursor: pointer;
  display: flex; align-items: center; gap: 6px;
  box-shadow: 0 1px 6px rgba(0,0,0,0.45);
}
.map-no-size-hint:hover { background: rgba(250, 204, 21, 1); }

/* ── Map overlays: light mode ── */
[data-mode="light"] .map-filter-panel { background: rgba(255,255,255,0.92); border-color: rgba(0,0,0,0.12); }
[data-mode="light"] .map-filter-toggle { color: #333; }
[data-mode="light"] .map-filter-toggle:hover { color: #000; }
[data-mode="light"] .map-filter-title { color: #555; }
[data-mode="light"] .map-filter-item { color: #333; }
[data-mode="light"] .map-filter-item:hover { color: #000; }
[data-mode="light"] .map-filter-sep { background: rgba(0,0,0,0.1); }
[data-mode="light"] .map-search-input { background: rgba(255,255,255,0.92); color: #111; border-color: rgba(0,0,0,0.15); }
[data-mode="light"] .map-search-input::placeholder { color: #999; }
[data-mode="light"] .map-search-results { background: rgba(255,255,255,0.95); border-color: rgba(0,0,0,0.12); }
[data-mode="light"] .map-search-item { color: #444; }
[data-mode="light"] .map-search-item:hover { background: rgba(0,0,0,0.05); color: #000; }
[data-mode="light"] .map-loading-box { background: rgba(255,255,255,0.95); color: #333; border-color: rgba(0,0,0,0.12); }
[data-mode="light"] .map-airport-count { background: rgba(255,255,255,0.85); color: #333; }
[data-mode="light"] .map-traffic-loading { background: rgba(255,255,255,0.98); color: #333; border-color: rgba(0,0,0,0.12); }
[data-mode="light"] .map-overlay-panel { background: rgba(255,255,255,0.92); border-color: rgba(0,0,0,0.12); }

/* ==================== INDUSTRIES DASHBOARD — ALERT ROWS ==================== */
/* The row of the Alerts tab: a red dot, the factory and its trouble on the
   left, Manage pinned to the right edge of a window-wide row. The layout used
   to be inline on the element, which made a hover tint impossible — an inline
   background outranks every class rule, and `:hover` cannot be written inline
   at all. Only the per-severity `border-left` stays inline now.
   Row pitch is unchanged on purpose: the list above is virtualized and
   measures its first row. */
.ind-alert-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  margin-bottom: 6px;
  background: var(--bg-input);
  border-radius: var(--radius-sm);
  transition: var(--transition-fast);
}
.ind-alert-row:hover {
  background-image: linear-gradient(var(--row-hover-overlay), var(--row-hover-overlay));
}

/* ==================== HAMBURGER & SIDEBAR OVERLAY ==================== */
.mobile-hamburger {
  display: none; /* shown only on mobile */
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 201;
  width: 40px;
  height: 40px;
  border: none;
  background: var(--bg-secondary);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  cursor: pointer;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.mobile-hamburger svg { width: 22px; height: 22px; }

.sidebar-overlay {
  display: none; /* shown only on mobile */
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  z-index: 99;
}

.titlebar-api-url {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
}

.titlebar-platform {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
  opacity: 0.75;
}

.titlebar-beta {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.6px;
  color: #f59e0b;
  background: rgba(245, 158, 11, 0.12);
  border: 1px solid rgba(245, 158, 11, 0.4);
  padding: 1px 6px;
  border-radius: 3px;
  white-space: nowrap;
  text-transform: uppercase;
  -webkit-app-region: no-drag;
  cursor: default;
}

/* ── Mode toggle button (sidebar) — hover effect ──
   Uses --mode-color (set via inline style as #9b59b6 freelance / #3b82f6 company)
   so the hover state stays color-coordinated with the current mode. */
.nav-mode-toggle:not(.is-locked):hover {
  background: color-mix(in srgb, var(--mode-color, #3b82f6) 25%, transparent) !important;
  border-color: var(--mode-color, #3b82f6) !important;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--mode-color, #3b82f6) 30%, transparent);
}
.nav-mode-toggle:not(.is-locked):hover .nav-mode-toggle-arrows {
  opacity: 1;
  transform: translateX(2px);
}
.nav-mode-toggle:not(.is-locked):active {
  transform: translateY(0);
  box-shadow: 0 1px 4px color-mix(in srgb, var(--mode-color, #3b82f6) 25%, transparent);
}

/* ==================== FILTER INPUT ==================== */
.filter-input {
  width: 320px;
  padding: 6px 12px;
  background: var(--bg-input);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13px;
}
.filter-input:focus { outline: none; border-color: var(--accent-blue); }

/* Clear (×) affordance for a list-filter box — ticket #10659.
   The × is explicit rather than `type="search"`: the native clear button is
   Chromium-only chrome, unstyled, and absent while the field is empty — which
   is precisely when a player needs to see whether the box is still armed.
   Same reasoning, and same geometry, as the Work Orders search (#10605).

   ⚠️ A class rather than the inline styles used there, because `.filter-input`
   carries its width in CSS (320px, and `width:100%` under 768px) instead of
   inline: an `inline-flex` wrapper with no width of its own would collapse the
   field on the mobile build. The mobile override below is what keeps that from
   happening — see the ≤768px block. */
.filter-input-wrap { position: relative; display: inline-flex; align-items: center; }
.filter-input-clear {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  padding: 2px 4px;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-muted);
  line-height: 1;
}
.filter-input-clear:hover { color: var(--text-primary); }

/* ==================== RESPONSIVE — TABLET (≤ 900px) ==================== */
@media (max-width: 900px) {
  .main-content { padding: 16px; }
  .screen-header { margin-bottom: 16px; }
  .screen-title { font-size: 20px; }
  .card { padding: 14px; }
  .flight-stats-grid { grid-template-columns: repeat(2, 1fr); }
  .filter-input { width: 180px; }
}

/* ==================== RESPONSIVE — MOBILE (≤ 768px) ==================== */
@media (max-width: 768px) {
  :root {
    --sidebar-width: 0px;
    --titlebar-height: 0px;
  }

  /* Hide Electron titlebar on mobile */
  .titlebar { display: none; }

  /* Hamburger visible */
  .mobile-hamburger { display: flex; }

  /* Sidebar: off-screen by default, slides in when .open */
  .sidebar {
    top: 0;
    width: 260px;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    z-index: 200;
  }
  .sidebar.open {
    transform: translateX(0);
  }
  .sidebar-overlay { display: block; }

  /* Main content: full width */
  .main-content {
    top: 0;
    left: 0;
    padding: 56px 14px 14px; /* top space for hamburger */
  }
  .web-mode .main-content { top: 0; left: 0; }

  /* Horizontal tabs: scrollable */
  .main-tabs {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    margin-bottom: 14px;
    padding-bottom: 8px;
    scrollbar-width: none;
  }
  .main-tabs::-webkit-scrollbar { display: none; }
  .main-tab-btn {
    padding: 7px 14px;
    font-size: 12px;
    white-space: nowrap;
    flex-shrink: 0;
  }

  /* Screen header */
  .screen-header { flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
  .screen-title { font-size: 18px; }
  .screen-subtitle { font-size: 12px; }
  .screen-actions { width: 100%; }
  .screen-actions .btn { flex: 1; justify-content: center; }

  /* Cards & grids */
  .card { padding: 12px; }
  .grid-2, .grid-3, .grid-4, .grid-2-1, .grid-1-2 { grid-template-columns: 1fr; gap: 10px; }
  .flight-stats-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; padding: 8px; }
  .conditions-triple { flex-wrap: wrap; }
  .conditions-triple-item { min-width: 80px; }

  /* Tables */
  .table-toolbar { flex-wrap: wrap; gap: 8px; }
  .filter-input { width: 100%; }
  /* The wrapper is the flex item here, so it has to carry the 100% too —
     otherwise the field it holds sizes itself against a shrink-to-fit box. */
  .filter-input-wrap { width: 100%; }

  /* Map — flex:1 handles height, no override needed */
  .map-search-panel { width: calc(100% - 20px); left: 10px; right: 10px; }
  .map-filter-panel { top: auto; bottom: 40px; right: 10px; left: 10px; min-width: unset; }
  /* The filter panel becomes a full-width bottom bar here — lift the layout
     capture prompt clear of it rather than letting the two overlap. */
  .airport-layout-empty-center { bottom: auto; top: 56px; }
  .map-filter-panel .map-filter-item { display: inline-flex; margin-right: 10px; }

  /* Airport popup */
  .airport-popup { max-width: calc(100% - 32px); min-width: unset; top: 10px; }

  /* Loading overlay */
  .loading-overlay .loader { padding: 20px; }
  .loader-text { font-size: 13px; }

  /* Empty state */
  .empty-state { padding: 30px 16px; }
  .empty-state-icon { width: 48px; height: 48px; }

  /* Dispatch form */
  .dispatch-form { max-width: 100%; }
}

/* ==================== RESPONSIVE — SMALL MOBILE (≤ 480px) ==================== */
@media (max-width: 480px) {
  .main-content { padding: 52px 10px 10px; }
  .screen-title { font-size: 16px; }
  .card { padding: 10px; }
  .flight-stats-grid { grid-template-columns: 1fr 1fr; gap: 6px; padding: 6px; }
  .flight-stat-value { font-size: 18px; }
  .flight-stat-label { font-size: 9px; }

  /* Tabs: smaller */
  .main-tab-btn { padding: 6px 10px; font-size: 11px; gap: 4px; }
  .main-tab-btn svg { width: 12px; height: 12px; }

  /* Map filter as horizontal row */
  .map-filter-panel { padding: 8px 10px; }
  .map-filter-title { display: none; }
  .map-filter-sep { display: none; }

  /* Airport popup */
  .airport-popup { padding: 10px 14px; font-size: 11px; }
  .airport-popup-stats { font-size: 10px; }
}

/* ── METAR Display ── */
.metar-display { padding: 12px 0; overflow: visible; }
.metar-loading, .metar-empty { color: var(--text-muted); font-size: 12px; padding: 8px 0; }
.metar-header { margin-bottom: 10px; overflow: visible; }
.metar-visual-row { display: flex; align-items: center; gap: 8px; overflow: visible; }
.metar-visual-row svg { overflow: visible; flex-shrink: 0; }
.metar-cat-label { font-weight: 700; font-size: 13px; }
.metar-wind-text { font-size: 12px; color: var(--text-secondary); }
.metar-info-grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 4px 16px; font-size: 12px; margin-bottom: 10px;
}
.metar-info-item { display: flex; align-items: baseline; gap: 8px; }
.metar-info-label { font-size: 10px; text-transform: uppercase; color: var(--text-muted); font-weight: 600; letter-spacing: 0.5px; white-space: nowrap; min-width: 72px; }
.metar-info-item > span:last-child { color: var(--text-primary); }
.metar-raw {
  font-family: 'IBM Plex Mono', monospace; font-size: 11px;
  color: var(--text-muted); word-break: break-all; line-height: 1.4;
  padding: 6px 8px; background: var(--bg-elevated); border-radius: var(--radius-sm);
}
.metar-compact { padding: 6px 0; }
.metar-compact .metar-visual-row { margin-bottom: 4px; }
.metar-raw-compact {
  font-family: 'IBM Plex Mono', monospace; font-size: 10px;
  color: var(--text-muted); word-break: break-all; line-height: 1.3;
}

/* ── Airport Search Suggestions ── */
.airport-suggestions {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 1000;
  background: var(--bg-elevated); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm); margin-top: 4px;
  max-height: 320px; overflow-y: auto;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.airport-suggestion-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; cursor: pointer; font-size: 12px;
  color: var(--text-secondary); transition: background 0.12s;
}
.airport-suggestion-item:hover {
  background: var(--bg-hover);
}
.airport-suggestion-item:not(:last-child) {
  border-bottom: 1px solid var(--border-subtle);
}

/* ============================================
   LEAFLET CSS (bundled locally — was CDN before)
   Source: node_modules/leaflet/dist/leaflet.css v1.9.4
   ============================================ */

/* required styles */

.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
	position: absolute;
	left: 0;
	top: 0;
	}
.leaflet-container {
	overflow: hidden;
	}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
	-webkit-user-select: none;
	   -moz-user-select: none;
	        user-select: none;
	  -webkit-user-drag: none;
	}
.leaflet-tile::selection {
	background: transparent;
}
.leaflet-safari .leaflet-tile {
	image-rendering: -webkit-optimize-contrast;
	}
.leaflet-safari .leaflet-tile-container {
	width: 1600px;
	height: 1600px;
	-webkit-transform-origin: 0 0;
	}
.leaflet-marker-icon,
.leaflet-marker-shadow {
	display: block;
	}
.leaflet-container .leaflet-overlay-pane svg {
	max-width: none !important;
	max-height: none !important;
	}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
	max-width: none !important;
	max-height: none !important;
	width: auto;
	padding: 0;
	}

.leaflet-container img.leaflet-tile {
	mix-blend-mode: plus-lighter;
}

.leaflet-container.leaflet-touch-zoom {
	-ms-touch-action: pan-x pan-y;
	touch-action: pan-x pan-y;
	}
.leaflet-container.leaflet-touch-drag {
	-ms-touch-action: pinch-zoom;
	touch-action: none;
	touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
	-ms-touch-action: none;
	touch-action: none;
}
.leaflet-container {
	-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
	-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
	filter: inherit;
	visibility: hidden;
	}
.leaflet-tile-loaded {
	visibility: inherit;
	}
.leaflet-zoom-box {
	width: 0;
	height: 0;
	-moz-box-sizing: border-box;
	     box-sizing: border-box;
	z-index: 800;
	}
.leaflet-overlay-pane svg {
	-moz-user-select: none;
	}

.leaflet-pane         { z-index: 400; }

.leaflet-tile-pane    { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane  { z-index: 500; }
.leaflet-marker-pane  { z-index: 600; }
.leaflet-tooltip-pane   { z-index: 650; }
.leaflet-popup-pane   { z-index: 700; }

.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg    { z-index: 200; }

.leaflet-vml-shape {
	width: 1px;
	height: 1px;
	}
.lvml {
	behavior: url(#default#VML);
	display: inline-block;
	position: absolute;
	}


/* control positioning */

.leaflet-control {
	position: relative;
	z-index: 800;
	pointer-events: visiblePainted;
	pointer-events: auto;
	}
.leaflet-top,
.leaflet-bottom {
	position: absolute;
	z-index: 1000;
	pointer-events: none;
	}
.leaflet-top {
	top: 0;
	}
.leaflet-right {
	right: 0;
	}
.leaflet-bottom {
	bottom: 0;
	}
.leaflet-left {
	left: 0;
	}
.leaflet-control {
	float: left;
	clear: both;
	}
.leaflet-right .leaflet-control {
	float: right;
	}
.leaflet-top .leaflet-control {
	margin-top: 10px;
	}
.leaflet-bottom .leaflet-control {
	margin-bottom: 10px;
	}
.leaflet-left .leaflet-control {
	margin-left: 10px;
	}
.leaflet-right .leaflet-control {
	margin-right: 10px;
	}


/* zoom and fade animations */

.leaflet-fade-anim .leaflet-popup {
	opacity: 0;
	-webkit-transition: opacity 0.2s linear;
	   -moz-transition: opacity 0.2s linear;
	        transition: opacity 0.2s linear;
	}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
	opacity: 1;
	}
.leaflet-zoom-animated {
	-webkit-transform-origin: 0 0;
	    -ms-transform-origin: 0 0;
	        transform-origin: 0 0;
	}
svg.leaflet-zoom-animated {
	will-change: transform;
}

.leaflet-zoom-anim .leaflet-zoom-animated {
	-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
	   -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
	        transition:         transform 0.25s cubic-bezier(0,0,0.25,1);
	}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
	-webkit-transition: none;
	   -moz-transition: none;
	        transition: none;
	}

.leaflet-zoom-anim .leaflet-zoom-hide {
	visibility: hidden;
	}


/* cursors */

.leaflet-interactive {
	cursor: pointer;
	}
.leaflet-grab {
	cursor: -webkit-grab;
	cursor:    -moz-grab;
	cursor:         grab;
	}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
	cursor: crosshair;
	}
.leaflet-popup-pane,
.leaflet-control {
	cursor: auto;
	}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
	cursor: move;
	cursor: -webkit-grabbing;
	cursor:    -moz-grabbing;
	cursor:         grabbing;
	}

/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
	pointer-events: none;
	}

.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
	pointer-events: visiblePainted;
	pointer-events: auto;
	}

/* visual tweaks */

.leaflet-container {
	background: #ddd;
	outline-offset: 1px;
	}
.leaflet-container a {
	color: #0078A8;
	}
.leaflet-zoom-box {
	border: 2px dotted #38f;
	background: rgba(255,255,255,0.5);
	}


/* general typography */
.leaflet-container {
	font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
	font-size: 12px;
	font-size: 0.75rem;
	line-height: 1.5;
	}


/* general toolbar styles */

.leaflet-bar {
	box-shadow: 0 1px 5px rgba(0,0,0,0.65);
	border-radius: 4px;
	}
.leaflet-bar a {
	background-color: #fff;
	border-bottom: 1px solid #ccc;
	width: 26px;
	height: 26px;
	line-height: 26px;
	display: block;
	text-align: center;
	text-decoration: none;
	color: black;
	}
.leaflet-bar a,
.leaflet-control-layers-toggle {
	background-position: 50% 50%;
	background-repeat: no-repeat;
	display: block;
	}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
	background-color: #f4f4f4;
	}
.leaflet-bar a:first-child {
	border-top-left-radius: 4px;
	border-top-right-radius: 4px;
	}
.leaflet-bar a:last-child {
	border-bottom-left-radius: 4px;
	border-bottom-right-radius: 4px;
	border-bottom: none;
	}
.leaflet-bar a.leaflet-disabled {
	cursor: default;
	background-color: #f4f4f4;
	color: #bbb;
	}

.leaflet-touch .leaflet-bar a {
	width: 30px;
	height: 30px;
	line-height: 30px;
	}
.leaflet-touch .leaflet-bar a:first-child {
	border-top-left-radius: 2px;
	border-top-right-radius: 2px;
	}
.leaflet-touch .leaflet-bar a:last-child {
	border-bottom-left-radius: 2px;
	border-bottom-right-radius: 2px;
	}

/* zoom control */

.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
	font: bold 18px 'Lucida Console', Monaco, monospace;
	text-indent: 1px;
	}

.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out  {
	font-size: 22px;
	}


/* layers control */

.leaflet-control-layers {
	box-shadow: 0 1px 5px rgba(0,0,0,0.4);
	background: #fff;
	border-radius: 5px;
	}
.leaflet-control-layers-toggle {
	background-image: url(images/layers.png);
	width: 36px;
	height: 36px;
	}
.leaflet-retina .leaflet-control-layers-toggle {
	background-image: url(images/layers-2x.png);
	background-size: 26px 26px;
	}
.leaflet-touch .leaflet-control-layers-toggle {
	width: 44px;
	height: 44px;
	}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
	display: none;
	}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
	display: block;
	position: relative;
	}
.leaflet-control-layers-expanded {
	padding: 6px 10px 6px 6px;
	color: #333;
	background: #fff;
	}
.leaflet-control-layers-scrollbar {
	overflow-y: scroll;
	overflow-x: hidden;
	padding-right: 5px;
	}
.leaflet-control-layers-selector {
	margin-top: 2px;
	position: relative;
	top: 1px;
	}
.leaflet-control-layers label {
	display: block;
	font-size: 13px;
	font-size: 1.08333em;
	}
.leaflet-control-layers-separator {
	height: 0;
	border-top: 1px solid #ddd;
	margin: 5px -10px 5px -6px;
	}

/* Default icon URLs */
.leaflet-default-icon-path {
	background-image: url(images/marker-icon.png);
	}


/* attribution and scale controls */

.leaflet-container .leaflet-control-attribution {
	background: #fff;
	background: rgba(255, 255, 255, 0.8);
	margin: 0;
	}
.leaflet-control-attribution,
.leaflet-control-scale-line {
	padding: 0 5px;
	color: #333;
	line-height: 1.4;
	}
.leaflet-control-attribution a {
	text-decoration: none;
	}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
	text-decoration: underline;
	}
.leaflet-attribution-flag {
	display: inline !important;
	vertical-align: baseline !important;
	width: 1em;
	height: 0.6669em;
	}
.leaflet-left .leaflet-control-scale {
	margin-left: 5px;
	}
.leaflet-bottom .leaflet-control-scale {
	margin-bottom: 5px;
	}
.leaflet-control-scale-line {
	border: 2px solid #777;
	border-top: none;
	line-height: 1.1;
	padding: 2px 5px 1px;
	white-space: nowrap;
	-moz-box-sizing: border-box;
	     box-sizing: border-box;
	background: rgba(255, 255, 255, 0.8);
	text-shadow: 1px 1px #fff;
	}
.leaflet-control-scale-line:not(:first-child) {
	border-top: 2px solid #777;
	border-bottom: none;
	margin-top: -2px;
	}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
	border-bottom: 2px solid #777;
	}

.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
	box-shadow: none;
	}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
	border: 2px solid rgba(0,0,0,0.2);
	background-clip: padding-box;
	}


/* popup */

.leaflet-popup {
	position: absolute;
	text-align: center;
	margin-bottom: 20px;
	}
.leaflet-popup-content-wrapper {
	padding: 1px;
	text-align: left;
	border-radius: 12px;
	}
.leaflet-popup-content {
	margin: 13px 24px 13px 20px;
	line-height: 1.3;
	font-size: 13px;
	font-size: 1.08333em;
	min-height: 1px;
	}
.leaflet-popup-content p {
	margin: 17px 0;
	margin: 1.3em 0;
	}
.leaflet-popup-tip-container {
	width: 40px;
	height: 20px;
	position: absolute;
	left: 50%;
	margin-top: -1px;
	margin-left: -20px;
	overflow: hidden;
	pointer-events: none;
	}
.leaflet-popup-tip {
	width: 17px;
	height: 17px;
	padding: 1px;

	margin: -10px auto 0;
	pointer-events: auto;

	-webkit-transform: rotate(45deg);
	   -moz-transform: rotate(45deg);
	    -ms-transform: rotate(45deg);
	        transform: rotate(45deg);
	}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
	background: white;
	color: #333;
	box-shadow: 0 3px 14px rgba(0,0,0,0.4);
	}
.leaflet-container a.leaflet-popup-close-button {
	position: absolute;
	top: 0;
	right: 0;
	border: none;
	text-align: center;
	width: 24px;
	height: 24px;
	font: 16px/24px Tahoma, Verdana, sans-serif;
	color: #757575;
	text-decoration: none;
	background: transparent;
	}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
	color: #585858;
	}
.leaflet-popup-scrolled {
	overflow: auto;
	}

.leaflet-oldie .leaflet-popup-content-wrapper {
	-ms-zoom: 1;
	}
.leaflet-oldie .leaflet-popup-tip {
	width: 24px;
	margin: 0 auto;

	-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
	filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
	}

.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
	border: 1px solid #999;
	}


/* div icon */

.leaflet-div-icon {
	background: #fff;
	border: 1px solid #666;
	}


/* Tooltip */
.leaflet-tooltip {
	position: absolute;
	padding: 6px;
	background-color: #fff;
	border: 1px solid #fff;
	border-radius: 3px;
	color: #222;
	white-space: nowrap;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	pointer-events: none;
	box-shadow: 0 1px 3px rgba(0,0,0,0.4);
	}
.leaflet-tooltip.leaflet-interactive {
	cursor: pointer;
	pointer-events: auto;
	}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
	position: absolute;
	pointer-events: none;
	border: 6px solid transparent;
	background: transparent;
	content: "";
	}

/* Directions */

.leaflet-tooltip-bottom {
	margin-top: 6px;
}
.leaflet-tooltip-top {
	margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
	left: 50%;
	margin-left: -6px;
	}
.leaflet-tooltip-top:before {
	bottom: 0;
	margin-bottom: -12px;
	border-top-color: #fff;
	}
.leaflet-tooltip-bottom:before {
	top: 0;
	margin-top: -12px;
	margin-left: -6px;
	border-bottom-color: #fff;
	}
.leaflet-tooltip-left {
	margin-left: -6px;
}
.leaflet-tooltip-right {
	margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
	top: 50%;
	margin-top: -6px;
	}
.leaflet-tooltip-left:before {
	right: 0;
	margin-right: -12px;
	border-left-color: #fff;
	}
.leaflet-tooltip-right:before {
	left: 0;
	margin-left: -12px;
	border-right-color: #fff;
	}

/* Printing */

@media print {
	.leaflet-control {
		-webkit-print-color-adjust: exact;
		print-color-adjust: exact;
		}
	}

/* ── Ongoing Flights page ── */
.flights-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 8px 0;
  margin-bottom: 8px;
  flex-wrap: wrap;
}
.flights-tabs {
  display: flex;
  gap: 2px;
  background: var(--bg-secondary);
  border-radius: var(--radius-sm);
  padding: 2px;
}
.flights-tab {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 16px;
  border: none;
  background: none;
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 500;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all 0.15s;
}
.flights-tab:hover { color: var(--text-primary); background: rgba(255,255,255,0.05); }
.flights-tab.active { background: var(--bg-card); color: var(--text-primary); box-shadow: var(--shadow-sm); }
.flights-toolbar-right {
  display: flex;
  align-items: center;
  gap: 16px;
}
.flights-ai-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-secondary);
  cursor: pointer;
  user-select: none;
}
.flights-ai-toggle input[type="checkbox"] {
  accent-color: #8b5cf6;
}
.flights-map-marker { background: none !important; border: none !important; }

/* ── Hover Card (popup on hover for ICAO / Aircraft links) ── */
.hover-card {
  position: fixed;
  z-index: 9999;
  min-width: 200px;
  max-width: 320px;
  padding: 12px 16px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 16px rgba(0,0,0,.45);
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-primary);
  pointer-events: auto;
  animation: hover-card-in .15s ease-out;
}
[data-mode="light"] .hover-card {
  background: #ffffff !important;
  border: 1px solid rgba(0,0,0,0.18) !important;
  box-shadow: 0 4px 16px rgba(0,0,0,0.18) !important;
  color: #1a1a1a !important;
}
[data-mode="light"] .hover-card * { color: #1a1a1a !important; }
[data-mode="light"] .hover-card .hover-card-subtitle { color: #4a4a4a !important; }
[data-mode="light"] .hover-card .hover-card-details { color: #5a5a5a !important; opacity: 1 !important; }
@keyframes hover-card-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.hover-card-title {
  font-weight: 700;
  font-size: 14px;
  margin-bottom: 2px;
}
.hover-card-subtitle {
  color: var(--text-secondary);
  margin-bottom: 6px;
  font-size: 12px;
}
.hover-card-details {
  font-size: 11px;
  opacity: 0.7;
  margin-bottom: 4px;
}
.hover-card-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 10px;
  font-size: 12px;
  line-height: 1.5;
}
.hover-card-label {
  color: var(--text-tertiary);
  white-space: nowrap;
}

/* ── Help panel body ── */
.help-panel-body h3 {
  margin: 18px 0 8px; font-size: 14px; font-weight: 700;
  color: var(--accent-cyan); letter-spacing: 0.3px;
}
.help-panel-body h3:first-child { margin-top: 0; }
.help-panel-body p { margin: 6px 0; }
.help-panel-body ul { margin: 6px 0; padding-left: 20px; }
.help-panel-body li { margin: 3px 0; }
.help-panel-body b { color: var(--text-bright); font-weight: inherit; }
.help-panel-body h4 {
  margin: 12px 0 4px; font-size: 12px; font-weight: 600;
  color: var(--text-secondary); letter-spacing: 0.2px;
}
.help-panel-body a {
  color: var(--accent-blue); text-decoration: none;
  border-bottom: 1px dotted rgba(59,130,246,0.4);
  transition: color 0.15s;
}
.help-panel-body a:hover { color: var(--accent-cyan); }

/* ============================================================
   QuantitySplitControl — partial cargo/PAX picker (dispatch)
   ============================================================ */
.qsc-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}
.qsc-static {
  display: inline-block;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  font-style: italic;
  cursor: help;
}
.qsc-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: 4px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text-primary);
  font-size: 11px;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.qsc-chip::after {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  margin-left: 2px;
  border-left: 3px solid transparent;
  border-right: 3px solid transparent;
  border-top: 4px solid currentColor;
  opacity: 0.6;
}
.qsc-chip:hover {
  background: rgba(245, 158, 11, 0.12);
  border-color: rgba(245, 158, 11, 0.7);
  color: #f59e0b;
}
.qsc-chip-split {
  background: rgba(245, 158, 11, 0.14);
  border-color: rgba(245, 158, 11, 0.55);
  color: #f59e0b;
  font-weight: 600;
}
.qsc-chip-split:hover {
  background: rgba(245, 158, 11, 0.22);
}
.qsc-chip-val { font-weight: 700; }
.qsc-chip-sep { opacity: 0.5; margin: 0 1px; }
.qsc-chip-max { opacity: 0.7; }
.qsc-chip-unit { opacity: 0.7; margin-left: 2px; }
/* #10628 — "480 units (12,000 lbs)": the weight a merchandise count stands for. */
.qsc-chip-weight { opacity: 0.7; font-weight: 400; }
.qsc-panel {
  position: fixed;
  z-index: 5000;
  min-width: 290px;
  padding: 10px 12px;
  background: var(--bg-elevated, var(--bg-card, #1f2937));
  border: 1px solid var(--border-medium);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.qsc-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border-subtle);
}
.qsc-panel-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: #f59e0b;
}
.qsc-panel-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--border-medium);
  font-size: 10px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: help;
  user-select: none;
}
.qsc-panel-help:hover {
  border-color: var(--accent-blue);
  color: var(--accent-blue);
}
.qsc-panel-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.qsc-slider {
  flex: 1;
  accent-color: #f59e0b;
  cursor: pointer;
}
.qsc-input {
  width: 70px;
  padding: 3px 6px;
  border: 1px solid var(--border-medium);
  border-radius: 3px;
  background: var(--bg-base);
  color: var(--text-primary);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.qsc-input::-webkit-outer-spin-button,
.qsc-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qsc-unit { font-size: 11px; color: var(--text-muted); }
.qsc-panel-actions { justify-content: space-between; }
.qsc-hint { font-size: 10px; color: var(--text-muted); }
.qsc-reset {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  font-size: 10px;
  border: 1px solid var(--border-medium);
  border-radius: 3px;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}
.qsc-reset:hover:not(:disabled) {
  border-color: var(--accent-blue);
  color: var(--accent-blue);
}
.qsc-reset:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.qsc-actions-right {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.qsc-validate {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.3px;
  border: 1px solid rgba(34, 197, 94, 0.6);
  border-radius: 3px;
  background: rgba(34, 197, 94, 0.15);
  color: #22c55e;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.qsc-validate:hover:not(:disabled) {
  background: rgba(34, 197, 94, 0.25);
  border-color: #22c55e;
}
.qsc-validate:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.qsc-spinner {
  display: inline-block;
  width: 10px;
  height: 10px;
  border: 1.5px solid rgba(34, 197, 94, 0.3);
  border-top-color: #22c55e;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
.qsc-error {
  margin-top: 4px;
  padding: 4px 6px;
  font-size: 10px;
  color: #f87171;
  background: rgba(248, 113, 113, 0.08);
  border: 1px solid rgba(248, 113, 113, 0.3);
  border-radius: 3px;
  word-break: break-word;
}

/* ── Cockpit Dispatch — titlebar button speaking pulse + visualizer ──
   The dispatch button shows an inline 4-bar visualizer left of the
   "Dispatch" text. Three states drive it :
     no tracking    → bars fully static (titlebar stays calm).
     tracking idle  → slow breath (channel-open cue) via
                       .dispatch-titlebar-btn-tracking.
     speaking       → fast, staggered dance + cyan frame + box-shadow
                       ripple via .dispatch-titlebar-btn-speaking. */

.dispatch-vu {
  display: inline-flex;
  align-items: flex-end;
  gap: 1.5px;
  height: 11px;
  vertical-align: middle;
}
.dispatch-vu .bar {
  display: inline-block;
  width: 2px;
  background: var(--text-secondary, #cbd5e1);
  border-radius: 1px;
  /* Full height, drawn squashed: animating `height` re-ran layout every frame (~350
     layouts/s, 11 % of the main thread, measured 2026-10-06); scaleY is composited.
     3px = 0.2727 of 11px. */
  height: 11px;
  transform: scaleY(0.2727);
  transform-origin: bottom;
}

.dispatch-titlebar-btn-tracking .dispatch-vu .bar {
  animation: dispatchBarBreath 3.2s ease-in-out infinite;
}
.dispatch-titlebar-btn-tracking .dispatch-vu .bar:nth-child(2) { animation-delay: 0.4s; }
.dispatch-titlebar-btn-tracking .dispatch-vu .bar:nth-child(3) { animation-delay: 0.8s; }
.dispatch-titlebar-btn-tracking .dispatch-vu .bar:nth-child(4) { animation-delay: 1.2s; }

.dispatch-titlebar-btn-speaking .dispatch-vu .bar {
  background: var(--accent-cyan, #06b6d4);
  animation: dispatchBarSpeak 0.55s ease-in-out infinite;
}
.dispatch-titlebar-btn-speaking .dispatch-vu .bar:nth-child(2) { animation-delay: 0.08s; }
.dispatch-titlebar-btn-speaking .dispatch-vu .bar:nth-child(3) { animation-delay: 0.22s; }
.dispatch-titlebar-btn-speaking .dispatch-vu .bar:nth-child(4) { animation-delay: 0.14s; }

@keyframes dispatchBarBreath {
  0%, 100% { transform: scaleY(0.2727); opacity: 0.55; }   /* 3px  */
  50%      { transform: scaleY(0.4545); opacity: 0.85; }   /* 5px  */
}
@keyframes dispatchBarSpeak {
  0%, 100% { transform: scaleY(0.2727); }                  /* 3px  */
  20%      { transform: scaleY(0.9091); }                  /* 10px */
  40%      { transform: scaleY(0.5455); }                  /* 6px  */
  60%      { transform: scaleY(1); }                       /* 11px */
  80%      { transform: scaleY(0.4545); }                  /* 5px  */
}

/* The halo used to animate a box-shadow's spread (0 -> 6px, alpha .55 -> 0). That is
   painted on the main thread every frame (~9 % of it, measured 2026-10-06). The same
   halo is now a STATIC shadow on a pseudo-element and the pulse only animates its
   opacity and scale, which the compositor does on its own. Shared by the dispatch
   button and the chat button. */
@keyframes oa-ring-pulse {
  0%, 100% { opacity: 0;   transform: scale(1); }
  50%      { opacity: 0.5; transform: scale(1.12); }
}
/* Gentle vertical bob so the button visibly "moves" while the dispatcher
   talks — the speaking cue now stands alone since dispatch sounds no longer
   auto-open the popup. */
@keyframes dispatchTitlebarBob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-1.5px); }
}
.dispatch-titlebar-btn-speaking::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: 0 0 0 4px rgba(6, 182, 212, 0.55);
  opacity: 0;
  animation: oa-ring-pulse 1.3s ease-in-out infinite;
}
.dispatch-titlebar-btn-speaking {
  position: relative;
  animation: dispatchTitlebarBob 0.65s ease-in-out infinite;
  border-color: var(--accent-cyan, #06b6d4) !important;
  color: var(--accent-cyan, #06b6d4) !important;
}

/* ── Titlebar communication shortcuts (Mailbox + Chat) ──
   Compact icon buttons that sit immediately to the right of the OnAir
   Companion logo. Each one shows an SVG glyph and an optional numeric
   badge for unread items. Chat additionally wiggles on every new
   message via .titlebar-chat-btn.has-unread (the parent uses a fresh
   `key` to retrigger the animation on each ping). */
.titlebar-comm-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 22px;
  padding: 0;
  background: none;
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  border-radius: 4px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.titlebar-comm-btn:hover:not(:disabled) {
  color: var(--text-primary);
  border-color: var(--text-secondary);
  background: rgba(255, 255, 255, 0.04);
}
.titlebar-comm-btn:disabled,
.titlebar-comm-btn.is-active {
  opacity: 0.55;
  cursor: default;
}
.titlebar-comm-btn.has-unread {
  color: var(--accent-blue, #3b82f6);
  border-color: rgba(59, 130, 246, 0.6);
}
.titlebar-comm-btn.titlebar-chat-btn.has-unread {
  color: var(--accent-cyan, #06b6d4);
  border-color: rgba(6, 182, 212, 0.6);
  animation: titlebarChatWiggle 0.55s ease-in-out 2;
}
.titlebar-comm-btn.titlebar-chat-btn.has-unread::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 4px;
  pointer-events: none;
  box-shadow: 0 0 0 4px rgba(6, 182, 212, 0.55);
  opacity: 0;
  animation: oa-ring-pulse 1.3s ease-in-out infinite;
}

/* Dispatcher voice controls — compact icon-only siblings of .titlebar-comm-btn,
   sitting next to the Dispatch button. Same square geometry + :hover so they
   read as obviously pressable; the label lives in the native title tooltip.
   Two of them: the speaking-only "stop current line" button (.stop / .done
   confirm flash) and the always-present mute toggle (.muted when silenced). */
.titlebar-voice-btn {
  -webkit-app-region: no-drag;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 22px;
  padding: 0;
  background: none;
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  border-radius: 4px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.titlebar-voice-btn:hover:not(:disabled) {
  color: var(--text-primary);
  border-color: var(--text-secondary);
  background: rgba(255, 255, 255, 0.04);
}
.titlebar-voice-btn.stop {
  color: var(--accent-red, #ef4444);
  border-color: rgba(239, 68, 68, 0.55);
  background: rgba(239, 68, 68, 0.14);
}
.titlebar-voice-btn.stop:hover:not(:disabled) {
  color: #f87171;
  border-color: rgba(239, 68, 68, 0.85);
  background: rgba(239, 68, 68, 0.22);
}
.titlebar-voice-btn.done {
  color: var(--accent-green, #22c55e);
  border-color: rgba(34, 197, 94, 0.5);
  background: rgba(34, 197, 94, 0.12);
  cursor: default;
}
.titlebar-voice-btn.muted {
  color: var(--accent-red, #ef4444);
  border-color: rgba(239, 68, 68, 0.45);
}
.titlebar-voice-btn.muted:hover:not(:disabled) {
  color: #f87171;
  border-color: rgba(239, 68, 68, 0.7);
  background: rgba(239, 68, 68, 0.1);
}

.titlebar-comm-badge {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 7px;
  background: var(--accent-blue, #3b82f6);
  color: #fff;
  font-size: 8px;
  font-weight: 700;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 1.5px var(--bg-titlebar);
}
.titlebar-chat-btn .titlebar-comm-badge {
  background: var(--accent-cyan, #06b6d4);
}

@keyframes titlebarChatWiggle {
  0%, 100% { transform: rotate(0deg); }
  15%      { transform: rotate(-10deg) scale(1.05); }
  30%      { transform: rotate(8deg)   scale(1.08); }
  45%      { transform: rotate(-6deg)  scale(1.05); }
  60%      { transform: rotate(4deg)   scale(1.03); }
  75%      { transform: rotate(-2deg); }
}

/* ── Sidebar build identity strip ──
   Moved here from the titlebar — sits below the Companion icon/label and
   bundles the app version, tier badge (Staff / Closed Beta / Open Beta),
   and the fake server label on a single compact row. */
.sidebar-build-strip {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 2px;
  font-family: 'IBM Plex Sans', sans-serif;
}
/* UTC clock — WEB ONLY, under the build strip (nav-bar.jsx). The desktop build
   shows it in the titlebar, which the web build does not render (#12084). */
.sidebar-utc {
  margin-top: 2px;
  text-align: center;
}
.sidebar-utc .titlebar-utc { font-size: 11px; }
/* The titlebar drops the date under 1500 px to save room (media query above);
   the sidebar has that room, so it keeps it — 0,2,0 beats the query's 0,1,0. */
.sidebar-utc .titlebar-utc-date { display: inline; }
.sidebar-build-version {
  font-size: 10px;
  color: var(--text-muted);
  opacity: 0.85;
  white-space: nowrap;
}
.sidebar-build-beta {
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.4px;
  padding: 1px 5px;
  border-radius: 3px;
  border: 1px solid;
  white-space: nowrap;
  cursor: default;
}
.sidebar-build-server {
  font-size: 10px;
  color: var(--text-muted);
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Web-mode banner (browser build only) ─────────────────────────────────
   Slim bottom-center pill mounted app-wide (login screen included) by
   components/web-mode-banner.jsx. Dismiss persists per-browser. Bottom
   placement avoids the top-center maintenance pill and the pinned flight
   strip. */
.web-mode-banner {
  position: fixed;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 950;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 14px;
  background: rgba(13, 20, 33, 0.92);
  border: 1px solid rgba(56, 152, 236, 0.35);
  border-radius: 999px;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.85);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
  max-width: min(92vw, 720px);
  white-space: nowrap;
}
.web-mode-banner .wmb-globe { font-size: 13px; }
.web-mode-banner .wmb-link {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: #3898ec;
  font-size: 12px;
  font-weight: 600;
  text-decoration: underline;
}
.web-mode-banner .wmb-close {
  background: none;
  border: none;
  padding: 0 0 0 2px;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.45);
  font-size: 12px;
  line-height: 1;
}
.web-mode-banner .wmb-close:hover { color: rgba(255, 255, 255, 0.85); }
/* Sous 720px la pastille centrée se rétrécissait au lieu de s'élargir : mesuré
   le 2026-09-17 sur le build web à 385px, 196px de large, un mot par ligne dans
   un ovale. `white-space: normal` seul ne suffisait pas — une boîte fixe
   dimensionnée par son contenu (left:50% + translateX) n'a pas de largeur à
   remplir. On l'ancre donc sur les deux bords et on en fait une barre. */
@media (max-width: 720px) {
  .web-mode-banner {
    white-space: normal;
    left: 8px;
    right: 8px;
    transform: none;
    max-width: none;
    border-radius: 10px;
    flex-wrap: wrap;
    justify-content: center;
    text-align: center;
    row-gap: 4px;
  }
}

/* ── Cookie consent (web build only) ─────────────────────────────────────────
   components/cookie-consent-banner.jsx. Bottom card ABOVE the web-mode pill
   (z 950), under every modal (10040+). No backdrop: the site works the same
   whether the visitor answers or not, so it must never block it.
   Centred with left/right + margin:auto, NOT a transform: a transformed fixed
   layer becomes the containing block of any fixed descendant
   (tests/fixed-layer-containing-block.test.mjs). */
.cookie-consent-banner {
  position: fixed;
  left: 0;
  right: 0;
  margin: 0 auto;
  bottom: 12px;
  z-index: 1000;
  width: min(calc(100vw - 16px), 640px);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 16px;
  background: #1a1f2e;
  border: 1px solid rgba(56, 152, 236, 0.35);
  border-radius: 10px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-secondary);
}
.cookie-consent-banner .ccb-text { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
.cookie-consent-banner .ccb-text strong { color: var(--text-primary); font-size: 13.5px; }
.cookie-consent-banner .ccb-hint { opacity: 0.8; font-size: 12px; }
.cookie-consent-banner .ccb-actions { display: flex; gap: 8px; flex-shrink: 0; }
.cookie-consent-banner .ccb-actions .btn { min-width: 96px; justify-content: center; }
@media (max-width: 720px) {
  .cookie-consent-banner { flex-direction: column; align-items: stretch; bottom: 8px; }
  .cookie-consent-banner .ccb-actions .btn { flex: 1; }
}

/* ── Lobby — structure + bascule mobile ──────────────────────────────────────
   Ces 3 règles remplacent des styles INLINE de lobby.jsx : en inline, aucune
   media query ne pouvait les atteindre, et la colonne NEWS (432px,
   flex-shrink:0) poussait « Choose your world » hors d'un écran de 375px sous
   un overflow:hidden — le lobby était inutilisable sur téléphone (aucune
   company atteignable). Même palier que le hamburger (768px). */
.lobby-shell {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  display: flex;
  background: transparent;
  overflow: hidden;
}
/* La colonne NEWS prend la largeur que les cartes de mondes n'utilisent pas.
   Les 3 cartes plafonnent à 320px (3×320 + 2×16 = 992px, + 56px de padding de
   .lobby-main) : au-delà, la place partait en marges vides des deux côtés.
   `100vw - 1100px` laisse ~50px de respiration autour de la grille ; 432px au
   plancher = l'ancienne largeur (rien ne bouge sous ~1530px de fenêtre) ;
   520px au plafond (décision Antoine 2026-09-12) : un plafond proportionnel
   (40vw, essayé) donnait une colonne et une image jugées trop grandes. L'image
   16/9 suit la colonne (100% moins 48px de padding), donc 472×266 au maximum. */
.lobby-news {
  width: clamp(432px, calc(100vw - 1100px), 520px);
  flex-shrink: 0;
  background: var(--bg-secondary);
  border-right: 1px solid var(--border-subtle);
  display: flex;
  flex-direction: column;
  padding: 24px;
  overflow-y: auto;
  min-height: 0;
}
.lobby-main {
  flex: 1;
  overflow-y: auto;
  padding: 24px 28px 40px;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.lobby-worlds {
  display: grid;
  grid-template-columns: repeat(3, minmax(238px, 320px));
  gap: 16px;
  align-items: stretch;
  flex: 1;
  align-content: center;
  justify-content: center;
}

@media (max-width: 768px) {
  /* Colonne unique : les MONDES d'abord (on vient pour jouer), les news
     ensuite — d'où le column-reverse, qui inverse l'ordre visuel sans toucher
     à l'ordre du DOM (donc sans changer l'ordre de tabulation ni la lecture
     par un lecteur d'écran, qui suivent le DOM). */
  .lobby-shell {
    flex-direction: column-reverse;
    overflow-y: auto;      /* le shell défile — sinon tout reste inatteignable */
    overflow-x: hidden;
  }
  .lobby-news {
    width: 100%;
    border-right: none;
    border-top: 1px solid var(--border-subtle);
    padding: 16px;
    overflow-y: visible;   /* plus de scroll imbriqué : le shell s'en charge */
    /* Hauteur dictée par le contenu, comme .lobby-main ci-dessous. */
    flex: 0 0 auto;
  }
  .lobby-main {
    padding: 16px 14px 24px;
    overflow-y: visible;
    /* CRUCIAL : sans ça, le `flex:1` du desktop survit en mode colonne et
       écrase la section à la hauteur restante (mesuré : boîte de 308px pour
       1555px de cartes). Avec overflow visible, le surplus se peignait PAR
       DESSUS la section NEWS, et le shell l'ignorait (scrollHeight ===
       clientHeight) donc rien ne défilait jusqu'à lui. En pile verticale la
       hauteur doit venir du contenu ; c'est le shell qui défile. */
    flex: 0 0 auto;
  }
  /* Les 3 colonnes de 238px min font 552px indéformables : sur un écran de
     375px les cartes débordaient à droite (mesuré scrollWidth 552 vs 357) et
     chevauchaient la section NEWS. Une seule colonne, largeur libre. */
  .lobby-worlds {
    grid-template-columns: 1fr;
    align-content: start;
  }
}

/* ── Transport animation (components/transport-animation.jsx) ────────────
   The great-circle line the pilot walks during the ~1s transport overlay.
   Styled here rather than through Leaflet's `color` option because a
   vector's stroke is an SVG presentation attribute: `var()` does not
   resolve there, and a literal hex would be wrong on ten of the eleven
   themes. */
.transport-arc {
  stroke: var(--accent-cyan);
}

/* ── Corps « liste + carte » (Goods Inventory, Factories) ─────────────────────
   Deux pages portent exactement le même corps à deux colonnes : la liste à
   gauche, la carte à droite. Leur géométrie vivait en style INLINE, et c'est
   précisément ce qui la rendait inatteignable : une règle de media query ne
   l'emporte pas sur un attribut `style`. Elle vit donc ici, non par goût du
   CSS, mais parce qu'un point de rupture ne peut pas exister autrement.

   ⚠️ Au-dessus de 768 px ces règles reproduisent EXACTEMENT ce que faisaient
   les styles inline — flex, 10 px de gouttière, carte à 42 % avec un plancher
   de 320 px pour Goods, 520 px fixes pour Factories. Rien ne bouge sur un
   écran normal, et la fenêtre Electron ne descend de toute façon jamais sous
   1200 px (main.js) : ce bloc ne peut mordre que dans le build navigateur.

   La largeur propre à chaque page passe par une variable posée en inline
   (`--map-split-w`, `--map-split-min`) plutôt que par une classe par page :
   la variable ne nourrit que la valeur PAR DÉFAUT, donc la règle d'empilement
   ci-dessous la remplace sans avoir besoin d'un `!important`. */
.map-split { display: flex; gap: 10px; flex: 1; min-height: 0; }
.map-split-list { flex: 1; min-width: 0; }
.map-split-map {
  width: var(--map-split-w, 42%);
  min-width: var(--map-split-min, 0px);
  flex-shrink: 0;
  border-radius: var(--radius-md);
  overflow: hidden;
}

@media (max-width: 768px) {
  /* Empilement : la liste au-dessus, la carte dessous. Le vrai défaut était le
     plancher — sur un écran de 375 px, Goods imposait 320 px de carte À CÔTÉ
     d'un tableau et Factories 520 px, donc un débordement garanti. Le lever ne
     suffisait pas : 42 % de 375, soit 157 px, ne fait pas une carte. */
  .map-split { flex-direction: column; }
  /* `min-height: 0` : en colonne, la valeur par défaut `auto` empêche un enfant
     de se réduire sous son contenu, et la liste cesserait de défiler. */
  .map-split-list { min-height: 0; }
  .map-split-map {
    width: 100%;
    min-width: 0;
    /* Hauteur FIXE, ni `vh` ni `%` : sur un navigateur mobile la barre d'URL
       fait varier `100vh` pendant le défilement, et un `%` exige un parent à
       hauteur définie — sans quoi la carte s'effondre à zéro et disparaît sans
       un bruit. 240 px garde la carte utile en laissant la liste lisible.
       Les deux pages observent leur conteneur (ResizeObserver ->
       safeInvalidateSize), donc Leaflet est prévenu du changement de boîte. */
    height: 240px;
  }
}

/* ── Panneau latéral qui s'empile sur un écran étroit (douze pages) ─────────
   Douze pages posent un panneau de largeur FIXE (200 à 460 px) à côté de leur
   contenu : Chat, Mailbox, Employee, Factory, Encyclopedia, Freelance Routes,
   Route Set, Work Order, Tour Map, Aircraft Market, Activities, création de
   compagnie. Sous ~500 px le contenu voisin était écrasé — la même famille que
   `.map-split` ci-dessus, dont le mécanisme est rappelé ici.

   ⚠️ Une règle de media query ne l'emporte pas sur un attribut `style`. On ne
   sort donc en CSS QUE ce que la règle d'empilement doit écraser : la largeur,
   le `flex-shrink:0`, une bordure latérale, et un `overflow:hidden` là où il
   rendrait du contenu inatteignable une fois la hauteur bornée. Tout le reste
   reste en inline — c'est ce qui garantit qu'au-dessus de 768 px chaque page
   rend exactement ce qu'elle rendait (mesuré, voir le test de garde).

   `.side-split` ne porte AUCUNE règle de bureau : c'est un crochet pour la
   media query. Le `display:flex` des parents reste inline, et comme aucun
   d'eux ne pose de `flex-direction` inline, la règle ci-dessous l'atteint.

   Les bordures ont un SENS : `--rule-end` borde un panneau placé AVANT le
   contenu (il passe au-dessus une fois empilé : filet dessous) ; `--rule-start`
   un panneau placé APRÈS (Tour Map : il passe dessous, filet dessus). */
.side-split-panel { width: var(--side-panel-w); flex-shrink: 0; }
.side-split-panel--rule-end { border-right: 1px solid var(--border-subtle); }
.side-split-panel--rule-start { border-left: 1px solid var(--border-subtle); }
.side-split-panel--clip { overflow: hidden; }

@media (max-width: 768px) {
  .side-split { flex-direction: column; }
  .side-split-panel {
    width: 100%;          /* sûr : reset universel en border-box */
    flex-shrink: 1;
    /* Une BORNE, pas une taille : un panneau court garde sa hauteur, un panneau
       long (liste de canaux, de dossiers, de recettes) ne pousse plus le
       contenu hors de l'écran — il défile dans ses 240 px. Pixels et non vh :
       la barre d'URL mobile fait varier 100vh pendant le défilement. */
    max-height: var(--side-panel-h, 240px);
    overflow-y: auto;
  }
  .side-split-panel--rule-end {
    border-right: none;
    border-bottom: 1px solid var(--border-subtle);
  }
  .side-split-panel--rule-start {
    border-left: none;
    border-top: 1px solid var(--border-subtle);
  }
  /* Un `overflow:hidden` + une hauteur bornée = du contenu coupé qu'on ne peut
     plus atteindre. Empilé, le panneau défile en hauteur et garde son
     découpage horizontal. */
  .side-split-panel--clip { overflow: hidden auto; }
}
