  *{box-sizing:border-box;margin:0;padding:0;}
  /* --user-font-scale (js/theme.js setFontScale()) is folded into zoom, not
     font-size: most font-sizes in this file are fixed px values, not
     em/rem, so they don't inherit a root font-size change. zoom scales
     everything uniformly instead, text included. */
  html{font-size:clamp(14px,1.1vw + 0.6rem,16px);zoom:calc(1.2 * var(--user-font-scale));}
  /* The gradient layer is a faint wash of the active vault's color
     (--vault-color-active-bg-faint, set on body by applyActiveVaultAccent()
     in app.js) — unset (transparent) on non-vault routes, Private Vault,
     and vaults with no color picked. */
  body{min-height:100vh;
    background:linear-gradient(var(--vault-color-active-bg-faint, transparent), var(--vault-color-active-bg-faint, transparent)) var(--bg);
    font-family:'Share Tech Mono','Courier New',monospace;color:var(--text-secondary);}

  :root{
    --accent:#00D4FF;      /* primary accent — main interactive color */
    --accent-2:#FF4FD8;    /* secondary accent — market/pink, used sparingly */
    /* Categorical ore palette for the mining composition bars — fixed
       order 1→6, never cycled (band 7+ uses --ore-more); the 2px segment
       gaps and legend swatches are the required secondary encoding since
       color alone isn't a safe channel for colorblind users. */
    --ore-1:#00A3CC;
    --ore-2:#C08A00;
    --ore-3:#C93BB0;
    --ore-4:#2F9E6E;
    --ore-5:#8A5FE0;
    --ore-6:#C25E33;
    --ore-more:rgba(255,255,255,0.35);
    --danger:#CC3333;
    --text-dim:#8A7EAC; /* lightened to clear 4.5:1 (AA) against both page and modal surfaces */
    --text:#D4C9E8; /* clears WCAG AAA (7:1) against page/modal/form-field backgrounds */
    /* Button system — see STYLE_GUIDE.md. Every clickable control (besides
       chips/badges, a deliberately distinct pill pattern for filters/tags)
       is built from .btn + one size + one variant class. */
    --btn-radius:4px;
    --btn-radius-icon:3px;
    --btn-font:12px;
    --btn-font-sm:10px;
    --btn-letter:1px;
    --btn-icon-size:28px;

    /* ── Theme surface tokens (light/dark/high-contrast, js/theme.js) ──────
       Dark is the unstyled default — see [data-theme="light"] and
       [data-contrast="high"] below for the other combinations. */
    --bg:#0C0714;
    --bg-input:#0D0716;
    --bg-panel:#150C20;
    --bg-dropdown:#1E1330;
    --border-strong:#3A2A50;
    --text-secondary:#B8AACE;
    --text-tertiary:#9A8CB8;
    --accent-hover:#33DFFF;
    --on-accent:#140A1C;
    --danger-text:#CC5555;
    --danger-text-hover:#E08080;
    --success:#3AE87A;
    --warning:#E8A93A;
    --component:#B06AFF;
    /* Distinct 4th hue for the marketplace/manifest "I" type-badge (Items of
       Interest) — with three kinds in one list (M/C/I) the badge needs to be
       told apart by color, not just the letter. */
    --interest:#D9B54D;
    --buy:#4FD8FF;
    --chip-text:#5A9AAA;
    --on-warning:#412402; /* dark-mode --warning is a bright amber (dark text reads on it); light/HC-light are dark olive/brown, so their overrides flip this to white */
    /* Raw (unwrapped) RGB triplets — every tinted-wash/border rule in this
       file reads rgba(var(--x-rgb),X) instead of a literal rgba(R,G,B,X),
       so one variable per hue flips every such wash to the active theme.
       --ore-* and the two always-dark hover-tooltips are deliberately NOT
       wired to this. */
    --overlay-rgb:255,255,255;
    --bg-rgb:12,6,18;
    --accent-rgb:0,212,255;
    --accent-2-rgb:255,79,216;
    --danger-rgb:204,51,51;
    --warning-rgb:232,169,58;
    --success-rgb:58,232,122;
    --component-rgb:176,106,255;
    --interest-rgb:217,181,77;
    --buy-rgb:79,216,255;
    --muted-rgb:88,72,112;
    --user-font-scale:1;
    /* Shared elevation shadows — every raised surface (tiles, dropdowns,
       panels, modals) reads one of these four instead of its own hand-picked
       rgba(0,0,0,X). A fixed black shadow reads as subtle on the dark
       theme's near-black surfaces but glarier on the light theme's
       near-white ones, so the light override below swaps to a softer
       palette-tinted shadow rather than just lowering opacity. */
    --shadow-tile:0 4px 12px rgba(0,0,0,0.35);
    --shadow-dropdown:0 8px 24px rgba(0,0,0,0.5);
    --shadow-panel:0 8px 40px rgba(0,0,0,0.6);
    --shadow-modal:0 20px 60px rgba(0,0,0,0.7);
  }

  /* Light theme — same token set, shapes/borders/spacing as dark; kept in
     the same accent hue family (STYLE_GUIDE: don't introduce a third accent
     hue) but darkened enough to clear WCAG AA against a near-white page. */
  [data-theme="light"]{
    --bg:#F3EFF9;
    /* Deliberately a hair darker than --bg-panel (#FFFFFF) — dark theme
       differentiates its own input/panel the same way (#0D0716 vs #150C20).
       An identical --bg-input would make fields disappear into .panel. */
    --bg-input:#F7F4FC;
    --bg-panel:#FFFFFF;
    --bg-dropdown:#FFFFFF;
    --border-strong:#D6CCE6;
    --accent:#00748F;
    --accent-2:#B0158F;
    --accent-hover:#005E73;
    --danger:#A32828;
    --danger-text:#A32828;
    --danger-text-hover:#7D1E1E;
    --success:#1E7A44;
    --warning:#8A5E00;
    --component:#6B3FBF;
    --interest:#8A6300;
    --buy:#006F87;
    --chip-text:#006F87;
    --on-warning:#FFFFFF;
    --text:#221731;
    --text-dim:#5C5072;
    --text-secondary:#382C4B;
    --text-tertiary:#5C5072;
    --on-accent:#FFFFFF;
    --overlay-rgb:20,10,30;
    --bg-rgb:243,239,249;
    --accent-rgb:0,116,143;
    --accent-2-rgb:176,21,143;
    --danger-rgb:163,40,40;
    --warning-rgb:138,94,0;
    --success-rgb:30,122,68;
    --component-rgb:107,63,191;
    --interest-rgb:138,99,0;
    --buy-rgb:0,111,135;
    --muted-rgb:148,138,168;
    /* Softer + palette-tinted (not pure black) — a black shadow at dark-
       theme's opacity reads as glary/heavy against a near-white surface. */
    --shadow-tile:0 2px 8px rgba(20,10,30,0.10);
    --shadow-dropdown:0 4px 14px rgba(20,10,30,0.14);
    --shadow-panel:0 6px 24px rgba(20,10,30,0.16);
    --shadow-modal:0 12px 36px rgba(20,10,30,0.20);
  }

  /* High contrast — layers on top of either base theme (independent toggle,
     js/theme.js setContrastMode()). Pushes "dim"/secondary text to the same
     near-max-contrast as primary text and swaps the panel/border wash for a
     solid border, targeting WCAG AAA (≥7:1) instead of the base themes' AA. */
  [data-theme="dark"][data-contrast="high"]{
    --bg:#000000;
    --bg-input:#000000;
    --bg-panel:#0A0510;
    --bg-dropdown:#0A0510;
    --border-strong:#FFFFFF;
    --accent:#4DEBFF;
    --accent-hover:#7FF3FF;
    --accent-2:#FF7FE8;
    --danger:#FF6B6B;
    --danger-text:#FF6B6B;
    --danger-text-hover:#FF9A9A;
    --success:#6BFFA0;
    --warning:#FFD166;
    --component:#C9A6FF;
    --interest:#FFDB70;
    --buy:#7FF3FF;
    --chip-text:#5A9AAA;
    --on-warning:#412402;
    --text:#FFFFFF;
    --text-dim:#FFFFFF;
    --text-secondary:#FFFFFF;
    --text-tertiary:#E5E5E5;
    --on-accent:#000000;
    --bg-rgb:0,0,0;
    --accent-rgb:77,235,255;
    --accent-2-rgb:255,127,232;
    --danger-rgb:255,107,107;
    --warning-rgb:255,209,102;
    --success-rgb:107,255,160;
    --component-rgb:201,166,255;
    --interest-rgb:255,219,112;
    --buy-rgb:127,243,255;
    --muted-rgb:255,255,255;
  }
  [data-theme="light"][data-contrast="high"]{
    --bg:#FFFFFF;
    --bg-input:#FFFFFF;
    --bg-panel:#FFFFFF;
    --bg-dropdown:#FFFFFF;
    --border-strong:#000000;
    --accent:#005A70;
    --accent-hover:#00748F;
    --accent-2:#8A0F72;
    --danger:#8A0000;
    --danger-text:#8A0000;
    --danger-text-hover:#6B0000;
    --success:#0F5C2E;
    --warning:#6B4700;
    --component:#4B1F91;
    --interest:#5C4300;
    --buy:#005A70;
    --chip-text:#005E73;
    --on-warning:#FFFFFF;
    --text:#000000;
    --text-dim:#000000;
    --text-secondary:#000000;
    --text-tertiary:#1A1A1A;
    --on-accent:#FFFFFF;
    --overlay-rgb:0,0,0;
    --bg-rgb:255,255,255;
    --accent-rgb:0,90,112;
    --accent-2-rgb:138,15,114;
    --danger-rgb:138,0,0;
    --warning-rgb:107,71,0;
    --success-rgb:15,92,46;
    --component-rgb:75,31,145;
    --interest-rgb:92,67,0;
    --buy-rgb:0,90,112;
    --muted-rgb:0,0,0;
  }
  /* Thicker borders + focus rings app-wide in high contrast — the low-
     opacity 1px borders used everywhere else are the main thing AA-vs-AAA
     contrast can't fix by recoloring alone. */
  [data-contrast="high"] .btn,[data-contrast="high"] .panel,
  [data-contrast="high"] input:not([type=checkbox]):not([type=radio]):not([type=range]),
  [data-contrast="high"] select,[data-contrast="high"] textarea{border-width:2px;}
  [data-contrast="high"] :focus-visible{outline-width:3px;}

  /* ── Button system ────────────────────────────────────────────────────
     .btn = shared box model for every button in the app.
     Size:    (default = md, ~38px tall) | .btn-sm (~28px) | .btn-icon (28x28, square)
     Variant: .btn-primary (solid accent — the one committing action per view)
              .btn-accent  (outlined accent — persistent page-level CTA)
              .btn-secondary (default ghost — Cancel, non-destructive)
              .btn-danger  (outlined red — destructive)
              .btn-ghost   (muted, for low-emphasis repeated controls)
     Variants may combine with .active (selected/toggled-on, accent-tinted). */
  .btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;
    font-family:inherit;font-size:var(--btn-font);letter-spacing:var(--btn-letter);
    padding:10px 16px;border-radius:var(--btn-radius);border:1px solid transparent;
    cursor:pointer;white-space:nowrap;line-height:1;background:none;color:var(--text-dim);
    transition:background .12s,border-color .12s,color .12s;}
  .btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
  .btn:disabled{opacity:0.35;cursor:not-allowed;}
  .btn-sm{padding:6px 10px;font-size:var(--btn-font-sm);border-radius:var(--btn-radius-icon);}
  .btn-icon{width:var(--btn-icon-size);height:var(--btn-icon-size);padding:0;flex-shrink:0;font-size:12px;border-radius:var(--btn-radius-icon);}
  /* Wraps a disabled icon button so its title tooltip is reachable on hover —
     disabled controls never receive mouseover/focus themselves, but the
     wrapper (not disabled) does. Sized to hug the button, not a layout box. */
  .disabled-tip-wrap{display:inline-flex;flex-shrink:0;}

  .btn-primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent);font-weight:700;}
  .btn-primary:hover{background:var(--accent-hover);}
  .btn-accent{background:rgba(var(--accent-rgb),0.1);border-color:rgba(var(--accent-rgb),0.35);color:var(--accent);}
  .btn-accent:hover{background:rgba(var(--accent-rgb),0.2);}
  .btn-secondary{background:none;border-color:rgba(var(--overlay-rgb),0.1);color:var(--text-dim);}
  .btn-secondary:hover{color:var(--text);border-color:rgba(var(--overlay-rgb),0.25);}
  .btn-danger{background:none;border-color:rgba(var(--danger-rgb),0.4);color:var(--danger-text);}
  .btn-danger:hover{background:rgba(var(--danger-rgb),0.12);color:var(--danger-text-hover);}
  .btn-ghost{background:rgba(var(--overlay-rgb),0.03);border-color:rgba(var(--overlay-rgb),0.08);color:var(--text-dim);}
  .btn-ghost:hover{color:var(--text);}
  .btn-ghost.active{background:rgba(var(--accent-rgb),0.10);border-color:rgba(var(--accent-rgb),0.35);color:var(--accent);}
  /* Marketplace scope tabs: a vault tab with a member-picked color
     (--vault-color set inline) highlights in its own color when active —
     everything else falls back to the .btn-ghost.active cyan above. */
  .market-scope-btn.active{background:var(--vault-color-bg, rgba(var(--accent-rgb),0.10));border-color:var(--vault-color, rgba(var(--accent-rgb),0.35));color:var(--vault-color, var(--accent));}
  .btn-ghost.danger:hover{color:var(--danger);border-color:rgba(var(--danger-rgb),0.4);background:rgba(var(--danger-rgb),0.08);}
  .btn-ghost.listed{color:var(--accent-2);border-color:rgba(var(--accent-2-rgb),0.4);background:rgba(var(--accent-2-rgb),0.08);}
  .btn-ghost.listed:hover{border-color:rgba(var(--accent-2-rgb),0.7);background:rgba(var(--accent-2-rgb),0.15);}
  /* Inline text-link button — for in-flow affordances that shouldn't look
     like a boxed button. */
  .link-btn{background:none;border:none;padding:0;font-family:inherit;font-size:inherit;color:var(--accent);cursor:pointer;text-decoration:none;}
  .link-btn:hover{text-decoration:underline;}

  /* ── App layout ──────────────────────────────────────────────────────
     Left sidebar (brand + persistent nav) + main area (topbar: add-entry/
     user-bar, then page content). See the sidebar-toggle/scrim rules and
     the max-width:900px query for the responsive collapse. */
  .app-layout{width:100%;display:flex;align-items:flex-start;min-height:100vh;}

  /* Deliberately stays translucent rgba(var(--bg-rgb),X), NOT var(--bg-panel)
     — body's own background is a per-vault-color gradient wash meant to
     inherit down and tint every surface faintly; a fully opaque sidebar
     would block that. */
  .sidebar{width:200px;flex-shrink:0;min-height:100vh;position:sticky;top:0;
    background:rgba(var(--bg-rgb),0.85);border-right:1px solid rgba(var(--overlay-rgb),0.08);
    padding:16px 12px;display:flex;flex-direction:column;gap:2px;overflow-y:auto;}
  .sidebar-brand{display:flex;align-items:center;gap:10px;padding:2px 6px 16px;
    border-bottom:1px solid rgba(var(--overlay-rgb),0.08);margin-bottom:10px;cursor:pointer;}
  .sidebar-brand-text{display:flex;flex-direction:column;gap:4px;min-width:0;}
  .brand-logo{height:48px;width:48px;flex-shrink:0;}
  .brand-title{font-family:'Orbitron',monospace;font-size:1.05rem;font-weight:900;color:var(--accent);letter-spacing:2px;}
  .beta-badge{font-size:10px;letter-spacing:1px;color:var(--warning);background:rgba(var(--warning-rgb),0.1);border:1px solid rgba(var(--warning-rgb),0.35);border-radius:8px;padding:1px 6px;white-space:nowrap;align-self:flex-start;}

  /* Persistent nav — rendered dynamically by renderTopNav() in app.js. */
  .sidebar-nav{display:flex;flex-direction:column;gap:3px;}
  .sidebar-section-label{font-size:10px;color:var(--text-dim);letter-spacing:1.5px;padding:12px 10px 4px;}
  .sidebar-link{display:flex;align-items:center;gap:9px;width:100%;text-align:left;
    padding:8px 10px;border-radius:6px;border-left:2px solid var(--vault-color, transparent);
    background:none;border-top:none;border-right:none;font-family:inherit;
    font-size:11px;letter-spacing:0.5px;color:var(--text-dim);cursor:pointer;
    transition:background .12s,color .12s,border-color .12s;}
  .sidebar-link i{font-size:12px;width:14px;text-align:center;flex-shrink:0;}
  /* Same accent-tinted-gradient hover already used on .home-card/.kpi-tile,
     so the nav reads as the same design generation as those surfaces
     instead of a flatter overlay tint left over from the old look. */
  .sidebar-link:hover{background:linear-gradient(rgba(var(--accent-rgb),0.05),rgba(var(--accent-rgb),0.05)),rgba(var(--overlay-rgb),0.03);color:var(--text);}
  /* Group Vault entries with a picked color (--vault-color, set inline per
     row) use it here too, so the active row highlights in its own color
     rather than always cyan; entries with no color fall back to --accent. */
  .sidebar-link.active{background:var(--vault-color-bg, rgba(var(--accent-rgb),0.1));border-left-color:var(--vault-color, var(--accent));color:var(--vault-color, var(--accent));}
  .sidebar-link .nav-badge{margin-left:auto;background:var(--warning);color:var(--on-warning);
    font-size:10px;font-weight:700;padding:1px 5px;border-radius:6px;}

  .main-area{flex:1;min-width:0;display:flex;flex-direction:column;}

  /* Topbar — the operations row + add-entry/user-bar, above the page
     content. The border spans the full main-area width, but the contents
     are constrained to the same max-width + padding as .main-content below,
     so the user-bar's right edge lines up with the content frame instead of
     drifting to the viewport edge. */
  .actionframe{width:100%;display:none;border-bottom:1px solid rgba(var(--overlay-rgb),0.06);}
  .actionframe.visible{display:block;}
  .actionframe-inner{display:flex;align-items:center;gap:10px;width:100%;
    max-width:1200px;margin:0 auto;padding:12px clamp(14px,3vw,24px);
    flex-wrap:wrap;}
  /* Per-vault context strip (app.js's renderVaultContextStrip()) — the
     old per-vault sidebar sub-links (Marketplace/Listings/Inventory/Chats/
     Storefront/Craftable Now/Event Vault), now horizontal and only shown
     while you're on one of that vault's own pages. Wraps onto a second
     line at narrow widths rather than needing its own mobile menu. */
  .ops-strip{display:flex;align-items:center;gap:4px;flex-wrap:wrap;}
  .ops-link{display:flex;align-items:center;gap:7px;padding:8px 12px;border-radius:6px;
    background:none;border:none;font-family:inherit;font-size:11px;letter-spacing:0.5px;
    color:var(--text-dim);cursor:pointer;white-space:nowrap;
    transition:background .12s,color .12s;}
  .ops-link i{font-size:12px;flex-shrink:0;}
  .ops-link:hover{background:linear-gradient(rgba(var(--accent-rgb),0.05),rgba(var(--accent-rgb),0.05)),rgba(var(--overlay-rgb),0.03);color:var(--text);}
  .ops-link.active{background:rgba(var(--accent-rgb),0.1);color:var(--accent);}
  .ops-link .nav-badge{background:var(--warning);color:var(--on-warning);
    font-size:10px;font-weight:700;padding:1px 5px;border-radius:6px;}
  /* The user bar anchors itself to the right edge, independent of whether
     the ops strip or an add-entry button is present before it. */
  #user-bar{margin-left:auto;}

  .main-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:14px;
    padding:clamp(14px,3vw,24px);max-width:1200px;width:100%;margin:0 auto;}
  /* Fleet build page opts out of the shared centered column — it's the one
     page in the app wide enough (multi-column category grid, §3 below) to
     want the full viewport width rather than capping at 1200px. .fc-page
     itself still caps at a sane max-width so it doesn't stretch unreadably
     wide on an ultrawide monitor. */
  .main-content:has(.fc-page){max-width:none;margin:0;}

  /* Mobile nav toggle — hidden on desktop, shown + sidebar collapsed to an
     off-canvas drawer under the 900px breakpoint (see media query below). */
  .sidebar-toggle{display:none;position:fixed;top:12px;left:12px;z-index:1100;
    width:36px;height:36px;border-radius:4px;background:var(--bg-panel);
    border:1px solid rgba(var(--overlay-rgb),0.12);color:var(--text);cursor:pointer;}
  .sidebar-scrim{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:999;}

  /* Dashboard (Home) cards — fixed grid, no drag-to-reorder yet. */
  .home-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;}
  /* KPI grids specifically (scoped via :has so the wider Home-page vault/
     shortcut cards above are untouched) get a narrower minimum than the
     200px default — a KPI tile is just a number + a short label, nowhere
     near as content-heavy as a .home-card. Without this, a 5th KPI tile
     commonly wraps to its own second row despite visible spare width. */
  .home-grid:has(.kpi-tile){grid-template-columns:repeat(auto-fit,minmax(150px,1fr));}
  /* Fleet build page (js/fleet.js) — its own visual system, a local --fc-*
     variable set scoped under .fc-page so it never leaks into any other
     page, distinct component classes (.fc-hero/.fc-layer/.fc-zone/
     .fc-slot-row/.fc-console/.fc-go/.fc-tag/.fc-chain), not a reskin of
     the shared .panel/.item-row/.btn system. */
  .fc-page{
    --fc-void:#0A0612; --fc-void-2:#130B20; --fc-edge:rgba(190,164,230,.16); --fc-edge-strong:rgba(190,164,230,.32);
    --fc-ink:#EDE6F7; --fc-ink-dim:#9A8CBE; --fc-ink-faint:#5C4E7C;
    --fc-cyan:#00E5FF; --fc-cyan-rgb:0,229,255; --fc-magenta:#FF4FD8; --fc-magenta-rgb:255,79,216;
    --glass-radius:10px; --glass-sheen-h:22px; --glass-sheen:rgba(255,255,255,.30);
    /* Glass material, matching the "Glass Console Tiles" mockup's own
       recipe (not a from-scratch derivation) — confirmed live 2026-08-10
       that a flat page background gives backdrop-filter:blur nothing to
       blur, so "glass" and "solid tint" were visually identical regardless
       of blur amount. The mockup's own page background is a textured
       aurora (radial-gradients + a diagonal base), and its glass adds
       saturate() on top of blur — both restored here, values taken
       directly from the mockup source rather than re-guessed. */
    --glass-blur:16px;
    --glass-fill-top:rgba(120,120,130,.16); --glass-fill-bottom:rgba(60,60,68,.10);
    --glass-rim:rgba(255,255,255,.4); --glass-rim-dim:rgba(200,200,210,.20);
    --glass-edge:rgba(10,10,14,.6);
    --glass-shadow:inset 0 1px 5px -1px var(--glass-rim), inset 0 -10px 14px -12px rgba(0,0,0,.5), 0 2px 0 -1px var(--glass-edge), 0 16px 34px -18px rgba(0,0,0,.7);
    background-image:
      radial-gradient(1100px 480px at 18% -8%, rgba(60,220,180,.14), transparent 62%),
      radial-gradient(900px 560px at 52% -4%, rgba(130,100,255,.13), transparent 60%),
      radial-gradient(1000px 520px at 88% 6%, rgba(0,200,225,.11), transparent 62%),
      radial-gradient(760px 820px at 102% 38%, rgba(190,80,210,.08), transparent 58%),
      radial-gradient(820px 680px at -8% 55%, rgba(50,200,160,.09), transparent 58%),
      linear-gradient(175deg, #060710 0%, #08091a 38%, #0A0612 100%);
    background-attachment:fixed;
    color:var(--fc-ink);border-radius:8px;font-size:15px;line-height:1.5;
  }
  [data-theme="light"] .fc-page{
    --fc-void:#EDE7FB; --fc-void-2:#FBF9FF; --fc-edge:rgba(90,60,140,.14); --fc-edge-strong:rgba(90,60,140,.28);
    --fc-ink:#221636; --fc-ink-dim:#5F4E82; --fc-ink-faint:#9384B3;
    --fc-cyan:#0092AD; --fc-cyan-rgb:0,146,173; --fc-magenta:#B01C8E; --fc-magenta-rgb:176,28,142;
    --glass-fill-top:rgba(95,95,102,.42); --glass-fill-bottom:rgba(95,95,102,.24);
    --glass-rim:rgba(255,255,255,.65); --glass-rim-dim:rgba(110,110,122,.22); --glass-sheen:rgba(255,255,255,.55);
    --glass-edge:rgba(90,90,100,.35);
    --glass-shadow:inset 0 1px 5px -1px var(--glass-rim), inset 0 -8px 12px -12px rgba(90,60,140,.3), 0 2px 0 -1px var(--glass-edge), 0 14px 30px -18px rgba(90,60,140,.4);
    background-image:
      radial-gradient(1100px 480px at 18% -8%, rgba(60,200,180,.10), transparent 62%),
      radial-gradient(900px 560px at 52% -4%, rgba(130,100,255,.08), transparent 60%),
      radial-gradient(1000px 520px at 88% 6%, rgba(0,180,205,.08), transparent 62%),
      radial-gradient(760px 820px at 102% 38%, rgba(190,80,210,.05), transparent 58%),
      linear-gradient(175deg, #F6F3FC 0%, #F0EBF9 45%, #EDE7FB 100%);
  }
  @media (prefers-color-scheme:light){
    :root:not([data-theme="dark"]) .fc-page{
      --fc-void:#EDE7FB; --fc-void-2:#FBF9FF; --fc-edge:rgba(90,60,140,.14); --fc-edge-strong:rgba(90,60,140,.28);
      --fc-ink:#221636; --fc-ink-dim:#5F4E82; --fc-ink-faint:#9384B3;
      --fc-cyan:#0092AD; --fc-cyan-rgb:0,146,173; --fc-magenta:#B01C8E; --fc-magenta-rgb:176,28,142;
      --glass-fill-top:rgba(95,95,102,.42); --glass-fill-bottom:rgba(95,95,102,.24);
      --glass-rim:rgba(255,255,255,.65); --glass-rim-dim:rgba(110,110,122,.22); --glass-sheen:rgba(255,255,255,.55);
      --glass-edge:rgba(90,90,100,.35);
      --glass-shadow:inset 0 1px 5px -1px var(--glass-rim), inset 0 -8px 12px -12px rgba(90,60,140,.3), 0 2px 0 -1px var(--glass-edge), 0 14px 30px -18px rgba(90,60,140,.4);
      background-image:
        radial-gradient(1100px 480px at 18% -8%, rgba(60,200,180,.10), transparent 62%),
        radial-gradient(900px 560px at 52% -4%, rgba(130,100,255,.08), transparent 60%),
        radial-gradient(1000px 520px at 88% 6%, rgba(0,180,205,.08), transparent 62%),
        radial-gradient(760px 820px at 102% 38%, rgba(190,80,210,.05), transparent 58%),
        linear-gradient(175deg, #F6F3FC 0%, #F0EBF9 45%, #EDE7FB 100%);
    }
  }
  /* High contrast (either theme): the app's own high-contrast palette is
     solid black/white with no translucent overlays at all (see
     [data-theme][data-contrast="high"] above) — a blurred, low-opacity
     glass fill fights that WCAG-driven design directly, so it's turned off
     here rather than kept "for consistency". Panels fall back to a plain
     solid fill + full-strength border, same shape as the rest of the app's
     high-contrast surfaces; the aurora background image is dropped too. */
  [data-contrast="high"] .fc-page{
    --glass-blur:0px; --glass-fill-top:var(--fc-void-2); --glass-fill-bottom:var(--fc-void-2);
    --glass-rim:var(--fc-edge-strong); --glass-rim-dim:var(--fc-edge-strong); --glass-shadow:none; --glass-sheen:transparent;
    background-image:none;background-color:var(--fc-void);
  }
  /* Mockup's own note (its "About This Material" card, and the comment on
     its .mark-glyph rule): cut/chamfer corners are gone in the real
     mockup — plain rounded corners read better and don't clip box-shadow
     the way clip-path does, so the glow lives directly on the element.
     Ours had drifted to keep the old cut-corner look; dropped here to
     match. */
  .fc-panel{
    position:relative;border-radius:var(--glass-radius);overflow:hidden;
    background:linear-gradient(180deg,var(--glass-fill-top) 0,var(--glass-fill-bottom) 110px,var(--glass-fill-bottom) 100%);
    backdrop-filter:blur(var(--glass-blur)) saturate(150%);-webkit-backdrop-filter:blur(var(--glass-blur)) saturate(150%);
    border:1px solid var(--glass-rim-dim);box-shadow:var(--glass-shadow);
  }
  /* The Aero sheen — a fixed-height glossy cap (never a percentage of the
     panel), same physical size on every panel regardless of how tall it
     is, matching the mockup's .glass::before exactly. */
  .fc-panel::before{
    content:"";position:absolute;left:1px;right:1px;top:1px;height:var(--glass-sheen-h);pointer-events:none;
    border-radius:calc(var(--glass-radius) - 1px) calc(var(--glass-radius) - 1px) 14px 14px;
    background:linear-gradient(180deg,var(--glass-sheen) 0%,rgba(255,255,255,.06) 60%,rgba(255,255,255,0) 100%);
  }

  /* The SWAP/ASSIGN + LINK picker modals opened from the Fleet build page
     (#fleet-picker-modal-overlay/#fleet-link-modal-overlay, fleet.js) are
     the only two modals scoped to this page — brought into the same Glass
     Console material as .fc-panel above. ID-scoped rather than reusing
     .fc-page (that class also carries page-layout rules that don't belong
     on a modal) so this can never leak onto any other modal in the app. */
  #fleet-picker-modal-overlay,#fleet-link-modal-overlay{
    --fc-void:#0A0612; --fc-void-2:#130B20; --fc-edge:rgba(190,164,230,.16); --fc-edge-strong:rgba(190,164,230,.32);
    --fc-ink:#EDE6F7; --fc-ink-dim:#9A8CBE; --fc-ink-faint:#5C4E7C;
    --fc-cyan:#00E5FF; --fc-cyan-rgb:0,229,255; --fc-magenta:#FF4FD8; --fc-magenta-rgb:255,79,216;
    --glass-blur:14px;
    --glass-fill-top:rgba(150,130,190,.14); --glass-fill-bottom:rgba(80,65,110,.10);
    --glass-rim:rgba(255,255,255,.28); --glass-rim-dim:rgba(190,164,230,.20);
    --glass-shadow:inset 0 1px 4px -1px var(--glass-rim), 0 10px 24px -14px rgba(0,0,0,.6);
  }
  [data-theme="light"] #fleet-picker-modal-overlay,[data-theme="light"] #fleet-link-modal-overlay{
    --fc-void:#F1ECFA; --fc-void-2:#FFFFFF; --fc-edge:rgba(90,60,140,.14); --fc-edge-strong:rgba(90,60,140,.28);
    --fc-ink:#221636; --fc-ink-dim:#5F4E82; --fc-ink-faint:#9384B3;
    --fc-cyan:#0092AD; --fc-cyan-rgb:0,146,173; --fc-magenta:#B01C8E; --fc-magenta-rgb:176,28,142;
    --glass-fill-top:rgba(120,120,130,.14); --glass-fill-bottom:rgba(120,120,130,.06);
    --glass-rim:rgba(255,255,255,.7); --glass-rim-dim:rgba(90,60,140,.18);
    --glass-shadow:inset 0 1px 4px -1px var(--glass-rim), 0 8px 20px -14px rgba(90,60,140,.35);
  }
  @media (prefers-color-scheme:light){
    :root:not([data-theme="dark"]) #fleet-picker-modal-overlay,:root:not([data-theme="dark"]) #fleet-link-modal-overlay{
      --fc-void:#F1ECFA; --fc-void-2:#FFFFFF; --fc-edge:rgba(90,60,140,.14); --fc-edge-strong:rgba(90,60,140,.28);
      --fc-ink:#221636; --fc-ink-dim:#5F4E82; --fc-ink-faint:#9384B3;
      --fc-cyan:#0092AD; --fc-cyan-rgb:0,146,173; --fc-magenta:#B01C8E; --fc-magenta-rgb:176,28,142;
      --glass-fill-top:rgba(120,120,130,.14); --glass-fill-bottom:rgba(120,120,130,.06);
      --glass-rim:rgba(255,255,255,.7); --glass-rim-dim:rgba(90,60,140,.18);
      --glass-shadow:inset 0 1px 4px -1px var(--glass-rim), 0 8px 20px -14px rgba(90,60,140,.35);
    }
  }
  [data-contrast="high"] #fleet-picker-modal-overlay,[data-contrast="high"] #fleet-link-modal-overlay{ --glass-blur:0px; --glass-fill-top:var(--fc-void-2); --glass-fill-bottom:var(--fc-void-2); --glass-rim:var(--fc-edge-strong); --glass-rim-dim:var(--fc-edge-strong); }

  #fleet-picker-modal-overlay .modal-box,#fleet-link-modal-overlay .modal-box{
    background:linear-gradient(180deg,var(--glass-fill-top) 0,var(--glass-fill-bottom) 110px,var(--glass-fill-bottom) 100%);
    backdrop-filter:blur(var(--glass-blur)) saturate(150%);-webkit-backdrop-filter:blur(var(--glass-blur)) saturate(150%);
    border:1px solid var(--glass-rim-dim);box-shadow:var(--glass-shadow);color:var(--fc-ink);
  }
  #fleet-picker-modal-overlay .modal-header,#fleet-link-modal-overlay .modal-header{background:none;border-bottom:1px solid var(--fc-edge);}
  #fleet-picker-modal-overlay .modal-title,#fleet-link-modal-overlay .modal-title{font-family:inherit;color:var(--fc-cyan);letter-spacing:.1em;}
  #fleet-picker-modal-overlay .modal-close,#fleet-link-modal-overlay .modal-close{color:var(--fc-ink-dim);}
  #fleet-picker-modal-overlay .modal-close:hover,#fleet-link-modal-overlay .modal-close:hover{color:var(--fc-ink);}
  #fleet-picker-modal-overlay .empty-state,#fleet-link-modal-overlay .empty-state{color:var(--fc-ink-dim);}
  #fleet-picker-modal-overlay .empty-state-hint,#fleet-link-modal-overlay .empty-state-hint{color:var(--fc-ink-faint);}

  /* Search box, filter pills, and result rows inside those two modals —
     same glass palette, this page's own component classes rather than the
     shared .btn/.item-row system (see this section's header comment). */
  .fc-search-input{width:100%;font-family:inherit;font-size:12.5px;color:var(--fc-ink);
    background:rgba(0,0,0,.15);border:1px solid var(--fc-edge);border-radius:4px;padding:8px 10px;}
  .fc-search-input::placeholder{color:var(--fc-ink-faint);}
  .fc-search-input:focus{outline:none;border-color:var(--fc-cyan);}

  .fc-pill{font-family:inherit;font-size:10.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
    border:1px solid var(--fc-edge);background:none;color:var(--fc-ink-dim);cursor:pointer;padding:5px 10px;border-radius:3px;}
  .fc-pill:hover{color:var(--fc-ink);border-color:var(--fc-ink-dim);}
  .fc-pill.active{color:var(--fc-void);background:var(--fc-cyan);border-color:transparent;}

  .fc-picker-list{display:flex;flex-direction:column;gap:1px;}
  .fc-picker-row{display:flex;align-items:center;justify-content:space-between;gap:12px;
    padding:9px 4px;border-bottom:1px solid var(--fc-edge);cursor:pointer;}
  .fc-picker-row:last-child{border-bottom:0;}
  .fc-picker-row:hover{background:rgba(var(--fc-cyan-rgb),.06);}
  .fc-picker-row-main{min-width:0;}
  .fc-picker-name{font-size:12.5px;font-weight:600;color:var(--fc-ink);}
  .fc-picker-mfg{font-size:10.5px;color:var(--fc-ink-faint);}
  .fc-picker-meta{display:flex;flex-wrap:wrap;gap:8px;font-size:10.5px;color:var(--fc-ink-dim);margin-top:2px;}
  .fc-picker-note{font-size:11px;color:var(--fc-ink-faint);padding:6px 2px;}

  .fc-hero{position:relative;padding:16px 20px 14px;overflow:hidden;}
  .fc-hero::after{content:"";position:absolute;top:-55%;right:-14%;width:340px;height:340px;border-radius:50%;
    background:radial-gradient(circle,rgba(var(--fc-cyan-rgb),.14),transparent 65%);pointer-events:none;}
  .fc-hero-top{display:flex;align-items:flex-end;justify-content:space-between;gap:18px;position:relative;flex-wrap:wrap;}
  .fc-eyebrow{font-size:10px;letter-spacing:.2em;color:var(--fc-cyan);text-transform:uppercase;display:flex;align-items:center;gap:8px;}
  .fc-eyebrow::before{content:"";width:16px;height:1px;background:var(--fc-cyan);}
  .fc-hero-title{font-size:26px;font-weight:800;letter-spacing:-.01em;color:var(--fc-ink);}
  .fc-hero-title-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:4px;}
  .fc-hero-sub{font-size:13px;color:var(--fc-ink-dim);margin-top:3px;}
  .fc-hero-actions{display:flex;align-items:center;gap:14px;flex:none;}

  .fc-go{font-family:inherit;font-size:12.5px;font-weight:700;border:1px solid var(--fc-edge-strong);
    background:none;color:var(--fc-ink-dim);cursor:pointer;padding:8px 13px;display:inline-flex;align-items:center;gap:6px;}
  .fc-go:hover{color:var(--fc-ink);border-color:var(--fc-ink-dim);}
  .fc-go-primary{color:var(--fc-void);background:linear-gradient(120deg,var(--fc-cyan),#4FC8FF);border-color:transparent;font-weight:800;padding:9px 16px;border-radius:7px;}
  .fc-go-primary:hover{filter:brightness(1.08);}

  /* Small labeled clusters (Flight Performance, Hull & Signature,
     Weaponry, Shields, Carrying Capacity, Quantum Travel) replacing the
     old single flat .fc-ticker row — more KPIs fit in the same width, and
     each group reads as its own real-world subsystem instead of one
     undifferentiated list (see fleetBuildStatsHtml()'s own comment for the
     build-hygiene tiles this dropped). */
  .fc-kpi-groups{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;position:relative;}
  .fc-kpi-group{flex:1;min-width:150px;padding:7px 10px;border-radius:6px;
    background:rgba(var(--fc-cyan-rgb),.04);border:1px solid var(--fc-edge);}
  .fc-kpi-group-head{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--fc-cyan);margin-bottom:5px;}
  .fc-kpi-group-row{display:flex;flex-wrap:wrap;gap:10px;}
  .fc-kpi-mini-label{font-size:9.5px;letter-spacing:.04em;color:var(--fc-ink-faint);text-transform:uppercase;}
  .fc-kpi-mini-value{font-size:16px;font-weight:800;color:var(--fc-ink);margin-top:2px;letter-spacing:-.01em;}
  /* A unit suffix right after a KPI number reads as too far away when it's
     just a plain space at the number's own big/bold size — smaller, dimmer
     and tight against the digits instead. */
  .fc-unit{font-size:10px;font-weight:600;color:var(--fc-ink-dim);margin-left:2px;letter-spacing:0;}
  .fc-tick-note{font-size:10.5px;color:var(--fc-ink-faint);}

  /* Weaponry table (Total/Pilot/Manned/Remote × DPS/Alpha/Sustained) — one
     flex item in .fc-kpi-groups, same width as every other cluster there
     (not a full-width block — that wasted too much of the row, confirmed
     live 2026-08-15), just with a small table inside instead of a row of
     mini tiles. */
  .fc-weaponry-panel{flex:1;min-width:150px;padding:7px 10px;border-radius:6px;
    background:rgba(var(--fc-cyan-rgb),.04);border:1px solid var(--fc-edge);}
  .fc-weaponry-head{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--fc-cyan);margin-bottom:5px;}
  .fc-weaponry-table{width:100%;border-collapse:collapse;}
  .fc-weaponry-table th{font-size:8px;letter-spacing:.02em;color:var(--fc-ink-faint);text-transform:uppercase;text-align:right;font-weight:600;padding:1px 0 2px;}
  .fc-weaponry-table th:first-child{text-align:left;}
  .fc-weaponry-table td{font-size:11px;font-weight:700;color:var(--fc-ink);text-align:right;font-variant-numeric:tabular-nums;padding:2px 0;white-space:nowrap;}
  .fc-weaponry-table td:first-child{text-align:left;font-weight:600;color:var(--fc-ink-dim);padding-right:4px;}
  .fc-weaponry-table tr + tr td{border-top:1px dashed var(--fc-edge);}
  .fc-weaponry-total td{font-size:15px;font-weight:800;color:var(--fc-cyan);}
  .fc-weaponry-total td:first-child{color:var(--fc-cyan);}

  /* Fixed Gimbal ("Locked") vs Manual/Auto Gimbal/Precision Targeting
     ("Gimbal") toggle, inline in the Weaponry group's own header. */
  .fc-fire-mode-toggle{float:right;display:inline-flex;gap:3px;}
  .fc-fire-mode-btn{font-family:inherit;font-size:8.5px;font-weight:700;letter-spacing:.06em;
    background:none;border:1px solid var(--fc-edge-strong);color:var(--fc-ink-faint);border-radius:3px;
    padding:1px 5px;cursor:pointer;}
  .fc-fire-mode-btn:hover{color:var(--fc-ink-dim);}
  .fc-fire-mode-btn.active{background:rgba(var(--fc-cyan-rgb),.15);border-color:var(--fc-cyan);color:var(--fc-cyan);}

  /* Quantum Travel calculator — real per-drive linear reference rates
     (FuelConsumptionSCUPerGM/TravelTime10GMSeconds), scaled to whatever
     distance the player types in. setFleetBuildQtDistance() only touches
     #fc-qt-calc-output on input, so the field never loses focus/cursor. */
  .fc-qt-calc{display:flex;flex-direction:column;gap:5px;}
  .fc-qt-input{font-family:inherit;font-size:11.5px;background:var(--fc-void-3,rgba(0,0,0,.18));
    border:1px solid var(--fc-edge);border-radius:4px;padding:4px 7px;color:var(--fc-ink);width:100%;box-sizing:border-box;}
  .fc-qt-result{display:flex;gap:14px;}
  .fc-qt-result-item{display:flex;flex-direction:column;}
  .fc-qt-result-value{font-size:13px;font-weight:700;color:var(--fc-ink);font-variant-numeric:tabular-nums;}
  .fc-qt-result-label{font-size:9px;letter-spacing:.04em;color:var(--fc-ink-faint);text-transform:uppercase;margin-top:1px;}

  .fc-ledger{margin-top:6px;}
  /* The drag handle for reordering category panels (fleetBuildInitCategoryDragOnce())
     — only this header bar is draggable, not the whole panel, so grabbing
     a row's own wrench/link/disclosure controls in the body below never
     mistakenly starts a panel drag. */
  .fc-ledger-head{display:flex;align-items:center;gap:6px;font-size:14px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--fc-cyan);padding:6px 6px 0;cursor:grab;}
  .fc-drag-grip{flex:none;font-size:11px;color:var(--fc-ink-faint);}
  .fc-panel.fc-dragging{opacity:.4;}
  .fc-ledger-body{padding:6px;}

  .fc-zone{margin-top:10px;}
  .fc-zone:first-child{margin-top:2px;}
  .fc-zone-head{display:flex;align-items:center;gap:8px;margin-bottom:6px;padding-bottom:6px;border-bottom:1px solid var(--fc-edge-strong);}
  .fc-zone-name{font-size:16.5px;font-weight:800;color:var(--fc-ink);display:flex;align-items:center;gap:9px;}
  .fc-zone-name::before{content:"";width:8px;height:8px;flex:none;background:var(--fc-cyan);border-radius:50%;}
  .fc-zone-note{font-size:11.5px;color:var(--fc-ink-faint);margin-left:auto;}
  /* A single turret position's own name, one level inside a Manned/Remote
     Turrets .fc-zone group — same weight as .fc-zone-name but no dot
     (that bullet marks a real grouping boundary; a position nested inside
     one already-dotted group doesn't need a second). */
  .fc-position-head{display:flex;align-items:center;gap:8px;margin-bottom:6px;padding:6px 0 6px 6px;border-bottom:1px solid var(--fc-edge-strong);}
  .fc-position-name{flex:none;min-width:0;font-size:15.5px;font-weight:800;color:var(--fc-ink);}
  /* One tile per root (weapon chain or any other category's own slot),
     side by side — mirrors the mockup's .zone-grid. 280px (up from 240px)
     gives the headline more room now that its batch pills sit inline on
     the same line rather than a row of their own. auto-fit, not
     auto-fill: with fewer items than fit a row, auto-fill still reserves
     the empty tracks at their minmax width, squeezing real items down to
     1/3 of the panel instead of letting them grow to fill it — confirmed
     live 2026-08-10 on a lone Cooler tile. auto-fit collapses the empty
     tracks so the 1fr items actually get the rest of the space; identical
     to auto-fill once there are enough items to fill every track. */
  /* align-items:start — without it, grid's default stretch makes every
     tile in a row match the row's tallest member, stretching a shorter
     tile's own grid cell into dead space below its real content. */
  .fc-zone-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:6px;align-items:start;}
  /* Depth reads as indentation plus a faint tier tint, not a connector
     line — CSS-drawn branch lines (tee/elbow marks, and before that a
     blurred carved card) kept fighting box-model edge cases (KPI-chip
     rows shifting a tick's target, a toggle icon's transparent glyph
     letting the line behind it bleed through) no amount of tuning fully
     fixed. .fc-chain-group is just the grid-item wrapper around a root +
     its descendants; .fc-chain wraps one nested node purely to carry its
     tier class, used by the tint rules below. */
  .fc-chain-group{display:flex;flex-direction:column;gap:2px;}

  /* .fc-layer* is the one tile/card rendering every category uses now
     (fleetBuildNodeRowHtml()) — the sidebar detail panel
     (fleetBuildConsoleHtml()), the ship_weapons zone grid (wrapped in
     .fc-chain), and every other category's own tile grid all share it. */
  .fc-layer{position:relative;padding:8px 12px;cursor:pointer;border-radius:6px;}
  /* One tint step deeper per nesting tier (fleetBuildChainTierClass(),
     capped at t3) — placed ahead of :hover/.selected below so those still
     read through on top at equal selector specificity, not the reverse. */
  .fc-chain-t1 > .fc-layer{background:rgba(237,230,247,.025);}
  .fc-chain-t2 > .fc-layer{background:rgba(237,230,247,.045);}
  .fc-chain-t3 > .fc-layer{background:rgba(237,230,247,.065);}
  .fc-layer:hover{background:rgba(var(--fc-cyan-rgb),.05);}
  .fc-layer + .fc-layer{border-top:1px solid var(--glass-rim-dim);}
  .fc-layer.selected{background:rgba(var(--fc-cyan-rgb),.08);}
  /* Left accent bar marks the selected row directly — replaces the old
     ancestor-card glow now that nesting has no card of its own to glow. */
  .fc-layer.selected::before{content:"";position:absolute;left:0;top:5px;bottom:5px;
    width:2.5px;border-radius:2px;background:var(--fc-cyan);}
  /* LINK + wrench (in that order, so LINK sits to the wrench's left) are a
     real flex child of the same row as the headline/batches, not an
     absolutely-positioned overlay — naturally level with the headline,
     no padding-reservation hack needed to keep content from running
     underneath a floating corner element. */
  .fc-layer-actions{display:flex;gap:6px;align-items:center;flex:none;}
  /* Stacks the batch pills above the LINK/wrench buttons instead of
     sharing one line with them — a fixed two-row block, so the same
     column always lands in the same place whether a row has 3 pills or
     1, 2 buttons or 1. */
  .fc-layer-side{display:flex;flex-direction:column;align-items:flex-end;gap:4px;flex:none;}
  .fc-layer-line{display:flex;align-items:center;gap:8px;margin-top:4px;}
  .fc-layer-line:first-of-type{margin-top:0;}
  .fc-layer-label{flex:1;min-width:0;font-size:14px;font-weight:700;color:var(--fc-ink-dim);letter-spacing:.02em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .fc-layer-fill{font-size:17.5px;font-weight:600;color:var(--fc-ink);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .fc-layer-fill.unassigned{color:var(--fc-ink-faint);font-weight:400;}
  /* The row's own real category (fleetBuildCategoryIconHtml()) — a
     cooler's fan, a shield's own icon, a gimbal's rotating mount distinct
     from the gun it's holding — so scanning down a chain reads faster
     than the name text alone. */
  .fc-row-icon{flex:none;width:15px;font-size:13px;text-align:center;color:var(--fc-ink-dim);}
  /* A shield's total HP / a cooler's own output, right next to the name
     (fleetBuildInlineStat()) — same size/weight/color as the row's own
     name text at that depth (.fc-layer-fill below), just not the name
     itself, so it reads as part of the headline rather than a KPI chip. */
  .fc-layer-inline-stat{flex:none;font-size:17.5px;font-weight:600;color:var(--fc-ink);white-space:nowrap;}
  /* A nested tile's own name (a mining laser's module, a quantum drive's
     sub-component) reads smaller and dimmer than its top-level parent's —
     .fc-chain-children is only ever a real parent/child wrapper (see
     fleetBuildNodeBlockHtml()'s own comment), so this needs no extra
     class in the markup, just scoping off that existing structure. */
  .fc-chain-children .fc-layer-fill{font-size:14px;color:var(--fc-ink-dim);}
  .fc-chain-children .fc-layer-fill.unassigned{color:var(--fc-ink-faint);}
  .fc-chain-children .fc-layer-inline-stat{font-size:14px;color:var(--fc-ink-dim);}
  .fc-pos-glyph{color:var(--fc-cyan);font-weight:700;}
  /* A via wrapper's own row (fleetBuildViaWrapperHtml()) — fixed hardware,
     never swappable, so it reads slightly dimmer than an editable layer. */
  .fc-layer-locked{cursor:default;}
  .fc-layer-locked .fc-layer-fill{color:var(--fc-ink-dim);}
  .fc-lock-icon{color:var(--fc-ink-faint);font-size:12px;}

  /* Wraps a node's children (fleetBuildNodeBlockHtml()) in a flex-column
     block — no padding of its own, so every row's own flex line still
     spans the panel's full width and its trailing batch pills/actions land
     at the same x-position regardless of nesting. Depth instead reads from
     each row's own leading .fc-depth-spacer plus the parent's tier tint
     (.fc-chain-t1/t2/t3 above), nothing drawn to connect the two. */
  .fc-chain-children{display:flex;flex-direction:column;gap:2px;}
  /* Per-row leading indent (fleetBuildNodeRowHtml()) — width set inline to
     depth*18px. Lives inside .fc-layer-line itself rather than on the
     .fc-chain-children wrapper so it can't shrink the row's own width. */
  .fc-depth-spacer{flex:none;}

  /* A classic disclosure chevron (fleetBuildTurretDisclosureHtml()) —
     points right/closed on a still-merged row, right into open/down on
     one of its now-individual members. Lives inline with the row's own
     content (in fc-position-head or fc-layer-line, both flex rows), never
     on a connector line, so there's nothing for it to collide with. */
  .fc-disclosure{flex:none;align-self:center;display:flex;align-items:center;justify-content:center;
    width:18px;height:18px;background:none;border:1px solid var(--fc-edge-strong);border-radius:4px;
    color:var(--fc-ink-dim);cursor:pointer;padding:0;}
  .fc-disclosure:hover{color:var(--fc-cyan);border-color:var(--fc-cyan);}
  .fc-disclosure svg{width:8px;height:8px;transition:transform .15s;}
  .fc-disclosure-open svg{transform:rotate(90deg);}

  /* Size/grade/class batch pills, inline at the right of the headline —
     replaces the old fixed-width Component/Size/Grade table columns,
     which sat mostly empty for a short value like "S3"/"GrC". Every pill
     shares one fixed width now (a blank .fc-batch-empty fills in for
     whichever a component doesn't have) — size, grade, and class all read
     as one consistent row instead of class standing out wider than its
     neighbors — so the wrench/link buttons after them land in the same
     x-position on every tile, regardless of which pills are present.
     align-items:center on the row (not the default stretch) keeps the
     pills at their own natural height instead of stretching to match the
     wrench/link buttons' taller icon box. */
  .fc-batch-row{display:flex;align-items:center;flex-wrap:wrap;gap:5px;flex:none;}
  /* height:22px + flex centering (not line-height/padding) matches
     .fc-wrench's own fixed 22px box exactly, so the pills and the wrench/
     link buttons land on the identical vertical center regardless of font
     metrics — line-height-based centering was landing a hair off. */
  .fc-batch{box-sizing:border-box;flex-shrink:0;display:flex;align-items:center;justify-content:center;width:34px;height:22px;font-size:10px;font-weight:700;letter-spacing:.04em;padding:0 4px;border-radius:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:center;}
  .fc-batch-size{background:rgba(var(--fc-cyan-rgb),.10);color:var(--fc-cyan);border:1px solid rgba(var(--fc-cyan-rgb),.28);}
  .fc-batch-grade{background:rgba(154,140,190,.14);color:var(--fc-ink-dim);border:1px solid var(--fc-edge-strong);}
  .fc-batch-class{background:rgba(var(--fc-magenta-rgb),.12);color:var(--fc-magenta);border:1px solid rgba(var(--fc-magenta-rgb),.3);}
  .fc-batch-empty{background:none;border-color:transparent;}

  .fc-tag{height:22px;box-sizing:border-box;display:inline-flex;align-items:center;font-size:10px;text-transform:uppercase;letter-spacing:.05em;padding:0 8px;flex:none;font-family:inherit;}
  /* Custodian-transfer linking (docs/ship-builds-overrides-design.md) —
     fc-tag-link mirrors fc-tag-assign's filled-magenta treatment for "plan
     needs an owned item"; fc-tag-linked is the outlined, already-fulfilled
     state (click to unlink). */
  .fc-tag-link{color:var(--fc-void);background:var(--fc-magenta);font-weight:800;cursor:pointer;border:0;}
  .fc-tag-linked{color:var(--fc-cyan);border:1px solid var(--fc-cyan);cursor:pointer;background:none;font-weight:700;}
  /* Expand/collapse toggle for a weapon mount's own sub-layers — currently
     unused (no call site enables it), kept for a future category. */
  .fc-chevron{background:none;border:0;color:var(--fc-ink-faint);cursor:pointer;padding:2px;font-family:inherit;}
  .fc-chevron:hover{color:var(--fc-ink);}
  .fc-chevron i{transition:transform .15s;display:block;}
  .fc-chevron.open i{transform:rotate(90deg);}

  .fc-grid{display:grid;grid-template-columns:1fr 330px;gap:7px;align-items:start;margin-top:10px;}
  .fc-grid > div > .fc-panel{margin-bottom:10px;}
  .fc-grid > div > .fc-panel:last-child{margin-bottom:0;}
  /* Category panels (fleetBuildListHtml() in fleet.js) — one panel per
     category, each holding a tile grid of its own roots, laid out in 3
     real DOM columns (fleetBuildComputePanelColumns()) rather than CSS
     multi-column masonry. Masonry (`columns:480px 3`) packed tightly like
     Pinterest, but rebalanced on every render — expanding one panel
     changed every column's total height, which the browser "fixed" by
     shuffling OTHER panels into different columns, reading as random
     reordering (confirmed live 2026-08-15). Plain CSS grid isn't an
     option either (confirmed live 2026-08-10): a tall panel forces its
     whole grid ROW to that height, leaving dead space under every
     shorter panel sharing that row. 3 explicit flex columns get the
     Pinterest-style packing back without the reshuffling — a panel's
     column is a stable, persisted (or default) decision, never a live
     height rebalance — and double as the drop targets for the
     drag-to-reorder handles below. */
  /* align-items default (stretch, not flex-start) so every .fc-cat-col
     matches the tallest column's height, not just its own content's —
     a short column's real empty space below its last panel is then part
     of its own box (droppable/hoverable for the panel drag-reorder, see
     fleetBuildDragOverPreview()) instead of belonging to nothing, which
     used to force dropping right on the last panel itself. */
  .fc-category-grid{display:flex;gap:7px;}
  .fc-cat-col{display:flex;flex-direction:column;gap:7px;flex:1;min-width:0;}
  @media (max-width:900px){ .fc-grid{grid-template-columns:1fr;} .fc-console-col{position:static !important;} .fc-category-grid{flex-direction:column;} }

  /* Sidebar column — Power Management widget (if any resource-networked
     component is assigned) stacked above the selected-item detail card,
     both scrolling together as one sticky unit. Spacing between them
     reuses the same .fc-grid > div > .fc-panel margin rule above, so no
     separate gap here (that would double up with it). */
  .fc-console-col{position:sticky;top:10px;}
  .fc-console{padding:10px;}
  .fc-console-empty{font-size:12px;color:var(--fc-ink-faint);text-align:center;padding:10px 0;}
  /* Identity header, ported from the mockup's .cx-top — a glyph avatar
     (first letter of the selected item's own name/label) plus name and
     zone/category subtitle, replacing the old plain eyebrow+title pair. */
  .fc-cx-top{display:flex;align-items:center;gap:12px;}
  .fc-cx-glyph{width:36px;height:36px;flex:none;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px;
    color:var(--fc-void);background:linear-gradient(135deg,var(--fc-cyan),#4FC8FF);border-radius:8px;}
  .fc-cx-name{font-size:13px;font-weight:800;color:var(--fc-ink);}
  .fc-cx-code{font-family:inherit;font-size:10px;color:var(--fc-ink-faint);margin-top:2px;}
  /* Recessed fact chips, ported from the mockup's .kv/.glass-inset — a
     shallow inset shadow, no blur/sheen of its own (stacking another full
     glass layer inside an already-glass card reads as unphysical). */
  .fc-kv-row{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-top:9px;}
  .fc-kv{padding:7px 8px;border-radius:4px;background:rgba(var(--fc-cyan-rgb),.05);box-shadow:inset 0 1px 3px rgba(0,0,0,.3);}
  .fc-kv-label{font-size:10px;color:var(--fc-ink-faint);text-transform:uppercase;letter-spacing:.05em;}
  .fc-kv-value{font-size:12px;font-weight:700;color:var(--fc-ink);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  /* Stat row format from the "Select vs Configure" mockup — label left
     (small caps, dim), value right (tabular numerals), dashed divider —
     replacing the old flat comma-separated fc-console-detail line and the
     generic <table> componentStatsTableHtml() used to render here. */
  .fc-console-body{padding-top:10px;}
  .fc-console-row{display:flex;justify-content:space-between;gap:12px;font-size:11px;padding:6px 0;border-bottom:1px dashed var(--fc-edge);}
  .fc-console-row:last-child{border-bottom:0;}
  .fc-console-row span:first-child{color:var(--fc-ink-faint);text-transform:uppercase;letter-spacing:.06em;font-size:10px;flex:none;}
  .fc-console-row span:last-child{color:var(--fc-ink);font-variant-numeric:tabular-nums;text-align:right;overflow:hidden;text-overflow:ellipsis;}

  /* Headline KPI chips (fleetBuildKpiChipsHtml()) — compact code+value
     pills on a tile/card, same visual language as the mockup's .kpi-c,
     colored by a coarse unit heuristic (fleetBuildKpiUnit()) rather than a
     per-field color table, since the underlying stat set is open-ended. */
  .fc-kpi-row{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px;}
  .fc-kpi-chip{display:flex;align-items:center;gap:4px;background:var(--fc-void-3,rgba(0,0,0,.18));border:1px solid var(--fc-edge);border-radius:4px;padding:2px 6px 2px 3px;cursor:help;}
  .fc-kpi-code{font-size:10px;font-weight:700;border-radius:2px;padding:1px 3px;line-height:1.3;color:#0A0612;}
  .fc-kpi-val{font-size:10.5px;font-weight:700;color:var(--fc-ink);font-variant-numeric:tabular-nums;}
  .fc-kpi-dmg{background:#FF8A4D;} .fc-kpi-hp{background:var(--fc-magenta);} .fc-kpi-rng{background:var(--fc-cyan);}
  .fc-kpi-time{background:#B98CFF;} .fc-kpi-pwr{background:#FFD23D;} .fc-kpi-rate{background:#2DD4BF;} .fc-kpi-mult{background:var(--fc-ink-dim);}

  /* Wrench — configure/swap, top-right corner of every tile via
     .fc-layer-top's flex row (not absolutely positioned). Same 34×22 box
     as a batch pill (.fc-batch above), not just the same height, so the
     actions row reads as one consistent size with the pills row above it. */
  .fc-wrench{width:34px;height:22px;flex:none;display:flex;align-items:center;justify-content:center;
    background:rgba(154,140,190,.12);border:1px solid var(--fc-edge-strong);border-radius:3px;color:var(--fc-ink-dim);
    cursor:pointer;font-family:inherit;transition:color .12s,border-color .12s,background .12s;}
  .fc-wrench:hover{color:var(--fc-cyan);border-color:var(--fc-cyan);background:rgba(var(--fc-cyan-rgb),.12);}
  .fc-wrench i{font-size:10px;}
  /* Same icon-button shape as the wrench, but persistently lit (not just
     on hover) — it's showing the tile is already linked, not a hover
     affordance. */
  .fc-wrench-linked{color:var(--fc-cyan);border-color:var(--fc-cyan);background:rgba(var(--fc-cyan-rgb),.12);}
  /* A merged row's LINK slot (fleetBuildNodeRowHtml()) — linking is a
     single instance's own relationship, so a merged "×N" row can't
     act on it, but keeps the slot's space reserved rather than
     collapsing the actions column by one. */
  .fc-wrench-disabled{opacity:.35;cursor:not-allowed;}
  .fc-wrench-disabled:hover{color:var(--fc-ink-dim);border-color:var(--fc-edge-strong);background:rgba(154,140,190,.12);}

  /* Power Management widget (fleetBuildPowerWidgetHtml()) — real
     stats.ResourceNetwork draw/pip data, see that function's own comment. */
  .fc-pwr-mini{padding:10px;}
  .fc-pwr-mini-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;}
  .fc-pwr-mini-title{font-size:14px;font-weight:800;color:var(--fc-ink);}
  .fc-pwr-mini-budget{font-size:15px;font-weight:800;color:var(--fc-cyan);}
  .fc-pwr-mini-budget span{color:var(--fc-ink-faint);font-size:10.5px;font-weight:600;}
  /* Compact segmented profile switch sitting between the title and budget
     figure — margin:0 keeps .type-toggle's own centering/margin-bottom
     rules from fighting this header's flex layout. */
  .fc-pwr-mode-toggle{margin:0;gap:4px;}
  /* Bigger hit target than the type-toggle default (SCM/NAV is switched
     often while tuning a build) — EM/IR's own row was dropped from this
     widget (already shown in the KPI header above) to make room. */
  .fc-pwr-mode-toggle button{min-width:0;padding:6px 14px;font-size:13px;}
  .fc-pwr-cols{display:flex;gap:8px;padding-top:14px;align-items:flex-end;flex-wrap:wrap;}
  .fc-pwr-col{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;min-width:0;}
  .fc-pwr-col.idle{opacity:.45;}
  .fc-pwr-col.off{opacity:.4;}
  /* Structurally blocked by the active SCM/NAV mode, not a user off-toggle
     — dimmer than .off and no pointer cursor on its pips, since clicking
     does nothing here (fleetBuildPowerWidgetHtml()'s locked branch omits
     data-action entirely). */
  .fc-pwr-col.locked{opacity:.3;}
  .fc-pwr-col.locked .fc-pwr-pip{cursor:default;}
  .fc-pwr-col.locked .fc-pwr-col-code{cursor:default;}
  .fc-pwr-col-n{font-size:11px;font-weight:700;color:var(--fc-ink);}
  .fc-pwr-col-stack{display:flex;flex-direction:column-reverse;gap:3px;width:24px;}
  .fc-pwr-pip{height:11px;width:100%;flex:none;border-radius:2px;background:var(--fc-edge);border:0;padding:0;cursor:pointer;font-family:inherit;}
  .fc-pwr-pip.on{background:linear-gradient(180deg,#4FC8FF,var(--fc-cyan));}
  .fc-pwr-pip.frame{background:none;border:1px dashed var(--fc-edge);}
  .fc-pwr-pip:hover{box-shadow:0 0 0 1px var(--fc-cyan);}
  .fc-pwr-col-code{font-size:10px;font-weight:800;letter-spacing:.04em;color:var(--fc-ink-faint);
    background:none;border:0;padding:0;cursor:pointer;font-family:inherit;}
  .fc-pwr-col-code:hover{color:var(--fc-cyan);}
  .fc-meter-row{display:flex;align-items:center;gap:8px;padding-top:12px;}
  .fc-meter-label{font-size:10px;color:var(--fc-ink-faint);text-transform:uppercase;letter-spacing:.04em;flex:none;width:52px;}
  .fc-meter-track{flex:1;height:6px;border-radius:3px;background:var(--fc-edge);overflow:hidden;}
  .fc-meter-fill{height:100%;border-radius:3px;background:linear-gradient(90deg,var(--fc-cyan),#4FC8FF);}
  .fc-meter-val{font-size:10px;font-weight:700;color:var(--fc-ink);flex:none;font-variant-numeric:tabular-nums;}
  .fc-meter-row.over .fc-meter-fill{background:var(--danger-text);}
  .fc-meter-row.over .fc-meter-val{color:var(--danger-text-hover);}
  /* Background is the same solid --bg-panel tone .panel's own body uses,
     not a low-opacity overlay wash — an overlay wash alone read as
     "hovering above the page" in dark mode but nearly flat in light mode,
     since a dark wash at the same low opacity barely shifts a near-white
     page. A solid fill plus --shadow-tile reads as a clearly raised
     surface in both themes. */
  .home-card{cursor:pointer;padding:16px;background:var(--bg-panel);border:1px solid rgba(var(--overlay-rgb),0.08);border-radius:6px;box-shadow:var(--shadow-tile);transition:border-color .15s,background .15s;display:flex;flex-direction:column;min-height:110px;}
  /* No transform on hover — a transform on this ancestor would make it the
     containing block for any position:fixed descendant, breaking the "⋯"
     row-menu dropdown's viewport-relative positioning math. Border-color/
     background already convey the hover state without a lift. */
  .home-card:hover{border-color:rgba(var(--accent-rgb),0.35);background:linear-gradient(rgba(var(--accent-rgb),0.05),rgba(var(--accent-rgb),0.05)),var(--bg-panel);}
  .home-card-head{display:flex;align-items:center;gap:9px;margin-bottom:6px;}
  .home-card-head i{font-size:14px;color:var(--accent);flex-shrink:0;}
  .home-card-label{font-size:12px;font-weight:700;letter-spacing:1px;color:var(--text);}
  .home-card-hint{font-size:12px;color:var(--text-dim);}
  /* Vault shortcut icons get their own bottom row — inline in the head row
     they crowded the vault name on narrow tiles. margin-top:auto pins the
     row to the tile's bottom edge so hints of different lengths don't push
     the icons around between tiles. */
  .home-card-actions{margin-top:auto;padding-top:10px;border-top:1px solid rgba(var(--overlay-rgb),0.06);display:flex;}
  .home-card-actions .vault-actions{margin-left:0;gap:6px;}
  .home-card-ghost{border-style:dashed;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;text-align:center;color:var(--text-dim);}
  .home-card-ghost:hover{color:var(--accent);transform:none;}
  .home-card-ghost i{font-size:16px;}
  .home-card-ghost .home-card-label{color:inherit;}

  /* Per-vault Dashboard KPI tiles — sit inside a plain .home-grid row (same
     responsive grid as the Home cards above), just with a static-number
     look instead of a clickable card. */
  .kpi-tile{padding:14px 16px;background:var(--bg-panel);border:1px solid rgba(var(--overlay-rgb),0.08);border-radius:6px;box-shadow:var(--shadow-tile);min-height:70px;display:flex;flex-direction:column;justify-content:center;transition:border-color .15s,background .15s;}
  .kpi-tile[data-action]:hover{border-color:rgba(var(--accent-rgb),0.35);background:linear-gradient(rgba(var(--accent-rgb),0.05),rgba(var(--accent-rgb),0.05)),var(--bg-panel);}
  .kpi-tile-value{font-size:22px;font-weight:700;color:var(--accent);line-height:1.2;}
  .kpi-tile-label{font-size:11px;color:var(--text-dim);margin-top:2px;}

  /* Home's card grid splits into zone-labeled groups — YOUR VAULTS /
     SHORTCUTS. Same small-caps treatment as .sidebar-section-label. */
  .grid-zone-label{font-size:11px;font-weight:700;letter-spacing:1.5px;color:var(--text-dim);text-transform:uppercase;margin:16px 0 8px;}
  .grid-zone-label:first-child{margin-top:0;}

  /* Crafting Plan "duo" — YOUR CRAFTING PLAN | MATERIALS NEEDED side by
     side, same shape as .uex-duo's own two-up layout. */
  .craft-plan-duo{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start;}
  @media (max-width:900px){.craft-plan-duo{grid-template-columns:1fr;}}
  .craft-plan-row-scoped td{background:rgba(var(--accent-rgb),0.06);}
  /* Crafting Plan folders (GENERAL + one per contributing ship build) —
     each is its own small table, stacked with a labeled head between them. */
  .craft-plan-folder + .craft-plan-folder{margin-top:16px;}
  .craft-plan-folder-head{display:flex;align-items:center;justify-content:space-between;font-size:10px;letter-spacing:2px;color:var(--text-dim);margin-bottom:6px;user-select:none;}
  .craft-plan-folder-head:hover{color:var(--text-secondary);}
  .inv-search{width:100%;margin-bottom:0;}

  /* App shell — fluid, centered, non-edge-to-edge main content */
  .status-line{font-size:10px;letter-spacing:1px;color:var(--text-dim);display:flex;gap:8px;align-items:center;}
  .status-dot{width:6px;height:6px;border-radius:50%;background:var(--component);}
  .status-dot.offline{background:var(--danger);}

  /* --vault-color-active/-bg (set on body by applyActiveVaultAccent() in
     app.js) tint every panel while a colored Group Vault is the active
     view — falls back to the flat border/dark header/dim header text on
     Private Vault, non-vault routes, or a vault with no color picked, since
     the var is unset in those cases. Header text is tinted too; every
     palette hex sits in the L 44-70% band, legible against the header bg. */
  .panel{width:100%;border:1px solid rgba(var(--overlay-rgb),0.08);border-left:3px solid var(--vault-color-active, transparent);border-radius:6px;overflow:hidden;background:var(--bg-panel);box-shadow:var(--shadow-panel);}
  /* No-color-picked fallback uses rgba(var(--overlay-rgb),X) rather than a
     fixed-opacity black — a flat black wash reads as a medium-gray band on
     the light theme that blends into the page instead of standing out;
     the relative-to-current-surface wash is the same technique every other
     "distinguish this band from its parent" rule in this file uses. */
  /* display:flex + space-between: the label and action-button group are
     laid out as two flex children instead of relying on inline text flow,
     so a wrapped .panel-actions group can't visually collide with the
     label text in a narrow column. flex-wrap drops the actions to their
     own full-width line instead. */
  .panel-header{padding:10px 16px;border-bottom:1px solid var(--border-strong);background:var(--vault-color-active-bg, rgba(var(--overlay-rgb),0.06));font-size:11px;letter-spacing:3px;color:var(--vault-color-active, var(--text-dim));display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;row-gap:8px;column-gap:16px;}
  .panel-header.clickable{cursor:pointer;user-select:none;}
  .panel-header.clickable:hover{color:var(--text-secondary);}
  .panel-header.clickable.active{color:var(--accent);background:rgba(var(--accent-rgb),0.08);}
  .panel-header.clickable.pink.active{color:var(--accent-2);background:rgba(var(--accent-2-rgb),0.08);}
  /* A panel-header's action button group — flex (not full-width block) so
     it sits directly after the header's label text, wrapping onto its own
     full-width line (courtesy of the parent's flex-wrap above) instead of
     overflowing or overlapping the label in a narrow grid column. */
  .panel-actions{display:flex;flex-wrap:wrap;gap:8px;}
  .header-hint{letter-spacing:1px;opacity:0.55;font-size:10px;}
  .panel-body{padding:14px 16px;}

  .summary-row{display:flex;align-items:center;gap:10px;padding:7px 10px;background:rgba(var(--overlay-rgb),0.06);border-left:2px solid var(--border-strong);border-radius:0 3px 3px 0;margin-bottom:4px;}
  .sum-mat{font-size:13px;font-weight:700;color:var(--text);width:130px;flex-shrink:0;}
  .sum-bands{display:flex;flex-wrap:wrap;gap:4px;flex:1;min-width:0;}
  .band-chip{font-size:11px;color:var(--text-secondary);background:rgba(var(--accent-rgb),0.05);border:1px solid rgba(var(--accent-rgb),0.15);border-radius:3px;padding:2px 7px;white-space:nowrap;font-family:inherit;cursor:pointer;}
  .band-chip b{color:var(--accent);font-weight:700;margin-right:4px;}
  .band-chip:not(.static):hover{background:rgba(var(--accent-rgb),0.15);color:var(--text);}
  .band-chip.active{background:var(--accent);border-color:var(--accent);color:var(--on-accent);}
  .band-chip.active b{color:var(--on-accent);}
  .band-chip.static{cursor:default;}
  .sum-scu{font-size:13px;color:var(--text);width:90px;text-align:right;flex-shrink:0;}
  .sum-count{font-size:10px;color:var(--text-tertiary);width:30px;text-align:right;flex-shrink:0;}

  /* Per-vault color picker (Vault Preferences modal) — one swatch per
     VAULT_PALETTE entry, background set inline in JS since every swatch
     shows a different color simultaneously. .selected marks the member's
     current pick for that vault. */
  .vault-swatch-row{display:flex;gap:6px;padding:4px 10px 10px;flex-wrap:wrap;}
  .vault-swatch{width:18px;height:18px;border-radius:50%;border:2px solid transparent;
    cursor:pointer;padding:0;transition:border-color .12s,transform .12s;}
  .vault-swatch:hover{transform:scale(1.15);}
  .vault-swatch.selected{border-color:var(--text);}
  /* "No color / back to default" chip — classic strike-through swatch.
     Selected (i.e. no color currently picked) still gets the same
     border ring as any other selected swatch. */
  .vault-swatch-default{background:linear-gradient(135deg, transparent 42%, var(--text-dim) 46%, var(--text-dim) 54%, transparent 58%) rgba(var(--overlay-rgb),0.04);}

  .vault-actions{display:flex;gap:2px;margin-left:auto;flex-shrink:0;}
  .vault-header{display:flex;align-items:center;gap:10px;padding:10px 16px;}
  .vault-header > i{color:var(--vault-color-active, var(--accent));font-size:14px;flex-shrink:0;}
  .vault-header-name{font-size:13px;font-weight:700;letter-spacing:2px;color:var(--text);}

  /* Personal budget widget (Private Vault header only) — sits between the
     vault name and the icon-button cluster; its own auto margin pushes it
     (and everything after it) flush right. */
  .vault-budget{display:flex;align-items:center;gap:6px;margin-left:auto;font-size:11px;color:var(--text-dim);cursor:pointer;white-space:nowrap;}
  .vault-budget i{color:var(--vault-color-active, var(--accent));font-size:12px;}
  .vault-budget-text{color:var(--text);font-weight:700;}
  .vault-budget-committed{color:var(--text-dim);}
  .vault-budget-over .vault-budget-committed{color:var(--danger);font-weight:700;}
  .vault-budget-editing{cursor:default;}
  .vault-budget-input{width:110px;font-size:11px;padding:3px 6px;}

  /* Search picker (storefront material items / group tax overrides) —
     type-to-filter results as a checkbox list; checking activates the
     material for configuration. */
  .picker-results{display:flex;flex-direction:column;gap:2px;margin-top:6px;max-height:180px;overflow-y:auto;}
  .picker-check{display:flex;gap:8px;align-items:center;font-size:12px;cursor:pointer;padding:4px 6px;border-radius:4px;}
  .picker-check:hover{background:rgba(var(--accent-rgb),0.06);}
  .picker-check-locked{cursor:default;opacity:0.75;}
  .picker-check-locked:hover{background:none;}

  /* Storefront modal — list level: one dense row per storefront, name +
     compact inline budget bar + meta + EDIT. */
  .sf-list-row{display:flex;align-items:center;gap:10px;padding:8px 10px;background:rgba(var(--overlay-rgb),0.06);border:1px solid rgba(var(--overlay-rgb),0.08);border-radius:4px;margin-bottom:4px;flex-wrap:wrap;}
  .sf-list-row-main{display:flex;align-items:center;gap:8px;flex:1;min-width:150px;flex-wrap:wrap;}
  .sf-list-row-bar{display:flex;align-items:center;gap:6px;width:110px;flex-shrink:0;}
  .sf-list-row-bar-track{flex:1;height:6px;border-radius:3px;background:rgba(var(--overlay-rgb),0.08);overflow:hidden;}
  .sf-list-row-bar-fill{height:100%;}
  .sf-list-row-actions{display:flex;gap:6px;align-items:center;flex-shrink:0;}

  /* Mining locations (#/mining) — expanded rock-type DETAILS block: per-ore
     composition range bars (fill spans the ore's min–max share of the rock,
     positioned inline via left/width percentages) plus a dimmed expected-
     inert remainder row. The fill is absolutely positioned to start
     mid-track, so these can't share classes with the width-only storefront
     budget bar. */
  /* Mining locations material-search results: a responsive TILE GRID
     (.mining-tile-grid/.mining-tile), distinct from the method-grouped
     browse ROWS above (.mining-row) — only the ranked search results use
     tiles. Each tile keeps the place+system (search spans multiple systems/
     planets, so it's never redundant here), a combined-chance headline,
     per-material chips, and the scan-for hint. */
  .mining-tile-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;}
  .mining-tile{border:1px solid rgba(var(--overlay-rgb),0.08);border-radius:6px;padding:10px;background:rgba(var(--overlay-rgb),0.06);display:flex;flex-direction:column;gap:6px;}
  .mining-tile-place{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
  .mining-tile-combined{font-size:20px;font-weight:700;color:var(--accent);white-space:nowrap;}
  .mining-tabbar-sub{border-bottom:none;margin-top:-6px;}
  .mining-tabbar-sub .tab{font-size:10px;padding:5px 10px;letter-spacing:1px;}
  .mining-controls{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;}
  /* CHANCE/NAME sort toggle above the browse view's method sections —
     small ghost-button pill, same .btn-ghost/.active language as
     craft-plan.js's comp-type toggle. */
  .mining-sort-toggle{display:flex;align-items:center;gap:6px;margin-bottom:10px;}
  .mining-search-pill-row{margin-bottom:10px;}
  .mining-cat{margin-bottom:14px;}
  /* 0.08, one step above the shared 0.06 list-row floor — this is a "head"
     row above plain .mining-row body rows and should stay visibly heavier
     than them, not fall to the same wash. */
  .mining-cat-head{display:flex;justify-content:space-between;align-items:baseline;gap:8px;padding:7px 10px;background:rgba(var(--overlay-rgb),0.08);border:1px solid rgba(var(--overlay-rgb),0.08);border-radius:6px 6px 0 0;font-size:11px;font-weight:700;letter-spacing:1.5px;}
  .mining-cat-note{color:var(--text-dim);font-weight:400;letter-spacing:0.5px;font-size:10px;}
  .mining-row{border:1px solid rgba(var(--overlay-rgb),0.06);border-top:none;}
  .mining-row:last-child{border-radius:0 0 6px 6px;}
  .mining-row.on{background:rgba(var(--overlay-rgb),0.06);}
  /* .mining-row-main is a role="button" DIV, not a <button> — the SIG chip
     is its own clickable control, and a <button> can't validly contain
     another <button>. Same visual treatment either way. */
  .mining-row-main{display:flex;align-items:center;flex-wrap:wrap;gap:8px;width:100%;padding:7px 10px;background:none;border:none;color:var(--text);font-family:inherit;font-size:12px;cursor:pointer;text-align:left;}
  .mining-row-main:hover{background:rgba(var(--overlay-rgb),0.03);}
  .mining-row-name{font-weight:700;letter-spacing:0.5px;min-width:140px;}
  .mining-row-chip{cursor:default;font-size:9px;padding:2px 7px;letter-spacing:1px;white-space:nowrap;}
  /* The SIG chip is the one chip that's actually clickable (toggles the
     scan-multiples strip below) — override .mining-row-chip's cursor:default
     and .chip's pill-button reset stays otherwise untouched. */
  .mining-sig-chip{cursor:pointer;}
  .mining-row-sigs{padding:2px 10px 6px 10px;font-size:10px;color:var(--text-dim);letter-spacing:0.3px;}
  .mining-row-bar{flex:1;height:6px;background:rgba(var(--overlay-rgb),0.06);border-radius:3px;overflow:hidden;min-width:60px;}
  .mining-row-bar span{display:block;height:100%;background:var(--accent);border-radius:3px;}
  .mining-row-pct{color:var(--accent);font-weight:700;white-space:nowrap;min-width:56px;text-align:right;}
  .mining-row-detail{padding:8px 12px 10px;border-top:1px dashed rgba(var(--overlay-rgb),0.08);display:flex;flex-direction:column;gap:6px;}
  /* .mining-loc-head is also a role="button" DIV — clicking it toggles
     .mining-loc-drill below, without turning the whole tile into a
     <button> (the mat-chip row and scan-for hint sit below it, outside the
     click target). */
  .mining-loc-head{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;cursor:pointer;}
  .mining-loc-head:hover{color:var(--accent);}
  .mining-loc-drill{margin-top:6px;padding-top:6px;border-top:1px dashed rgba(var(--overlay-rgb),0.08);display:flex;flex-direction:column;gap:4px;}
  .mining-drill-cat{display:flex;flex-direction:column;gap:2px;}
  .mining-drill-row{display:flex;align-items:baseline;gap:8px;font-size:11px;flex-wrap:wrap;}
  .mining-drill-name{font-weight:700;color:var(--text);}
  .mining-drill-pct{color:var(--accent);}
  .mining-loc-name{font-weight:700;font-size:12px;letter-spacing:0.5px;}
  /* Own line below .mining-loc-head — the combined-chance headline lives
     in the head row instead, so no margin-left:auto needed here. */
  .mining-loc-chips{display:flex;gap:6px;flex-wrap:wrap;}
  .mining-mat-chip{font-size:10px;padding:2px 8px;border-radius:10px;border:1px solid rgba(var(--accent-rgb),0.25);color:var(--accent);background:rgba(var(--accent-rgb),0.06);letter-spacing:0.5px;white-space:nowrap;}
  .mining-mat-chip.miss{opacity:0.4;border-color:rgba(var(--overlay-rgb),0.15);color:var(--text-dim);background:transparent;}
  .mining-best-line{font-size:11px;letter-spacing:0.5px;color:var(--text-dim);border:1px solid rgba(var(--accent-rgb),0.25);background:rgba(var(--accent-rgb),0.05);border-radius:6px;padding:7px 10px;margin-bottom:8px;}
  .mining-best-line strong{color:var(--accent);}
  .mining-env{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px;}
  .mining-env-chip{cursor:default;font-size:9px;letter-spacing:1px;padding:3px 8px;}
  .mining-card-meta{font-size:10px;color:var(--text-dim);letter-spacing:0.5px;}
  .mining-stack{display:flex;gap:2px;height:10px;border-radius:3px;overflow:hidden;}
  .mining-stack-seg{min-width:3px;}
  .mining-stack-seg.inert{background:rgba(var(--overlay-rgb),0.10);}
  /* One flowing line, not a stacked list — entries are inline-flex spans,
     " · " text nodes between them fall in as normal flow text, and
     flex-wrap lets the whole thing wrap onto a second line on narrow
     panels instead of one entry per row. */
  .mining-legend{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;font-size:11px;color:var(--text-dim);}
  .mining-legend-row{display:inline-flex;align-items:center;gap:5px;}
  .mining-legend-mat{color:var(--text);}
  .mining-legend-row.hl .mining-legend-mat{color:var(--accent);}
  .mining-legend-row.inert{opacity:0.6;}
  .mining-legend-row.inert .mining-swatch{background:rgba(var(--overlay-rgb),0.10);}
  .mining-swatch{width:8px;height:8px;border-radius:2px;flex-shrink:0;}
  .mining-legend-text{min-width:0;}

  /* QUALITY ROLL block (expanded rock detail): the game's real Normal(mean,
     stddev) quality-roll curve for that mining method/tier/system, shown as
     EITHER a segmented per-Q-band odds bar (.mining-qr-bands, v2, below) OR
     — when the dominant ore's own quality bands aren't known — the v1
     continuous density gauge, with the "typical ~N" median folded into the
     QUALITY ROLL label itself (a system-override curve, if any, applies
     silently to the numbers without its own chip). Wraps on narrow
     screens; the v1 gauge keeps a floor width so it never collapses to
     nothing. Gradient stops for the v1 gauge are inline (computed per rock
     in JS) — only the track chrome and tick live here. Colors are the
     app's one --accent, same as every other mining accent on this page. */
  .mining-quality-roll{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:2px 0 4px;}
  .mining-quality-gauge{position:relative;flex:1 1 90px;min-width:90px;height:6px;border-radius:3px;overflow:hidden;background:rgba(var(--overlay-rgb),0.06);}
  .mining-qr-tick{position:absolute;top:0;bottom:0;width:2px;background:rgba(var(--accent-rgb),0.9);}

  /* QUALITY ROLL v2: a segmented per-Q-band odds bar replaces the single
     jackpot pill when the dominant ore's own bands are known — falls back
     to .mining-quality-gauge above otherwise. One .mining-qr-band per band:
     label above, brightness-coded strip in the middle, "1 in N" odds
     below. flex-basis:0 + inline flex-grow gives proportional widths, and
     min-width is the overlap fix — the top bands of a typical ore occupy
     only ~10% of the roll range, so proportional widths alone would
     collide; forcing a floor makes overlap impossible by construction
     (accepted width distortion, same tradeoff any compressed axis makes).
     flex-basis:100% wraps the row onto its own line, same as the v1
     gauge does via the parent's flex-wrap. */
  .mining-qr-bands{display:flex;gap:2px;flex:1 1 100%;min-width:0;overflow-x:auto;padding:2px 0;}
  .mining-qr-band{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:44px;flex-shrink:0;flex-basis:0;}
  .mining-qr-band-label{font-size:9px;letter-spacing:0.5px;color:var(--text-dim);}
  .mining-qr-band-strip{height:6px;border-radius:3px;width:100%;}
  .mining-qr-band-odds{font-size:9px;color:var(--text-dim);white-space:nowrap;}
  .mining-qr-band.median .mining-qr-band-strip{outline:1px solid rgba(var(--accent-rgb),0.7);}
  .mining-qr-band.median .mining-qr-band-label{color:var(--accent);}

  /* Locations sharing byte-identical stats collapse into one line reading
     "N identical spots"; clicking it reveals the individual place names
     below. .mining-location-group wraps the line (chip-note gives it the
     same sizing/color as a plain location line); the toggle is styled as
     inline text, not a boxed chip, so it reads as part of the sentence
     it's replacing the place name in. */
  .mining-location-group-toggle{background:none;border:none;padding:0;margin:0;font:inherit;font-size:11px;color:var(--accent);cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px;}
  .mining-location-group-toggle:hover{color:var(--text);}
  .mining-location-group-names{padding:2px 0 6px 4px;color:var(--text-dim);font-size:10.5px;letter-spacing:0.2px;}

  /* Storefront modal — edit level item rows: the row itself uses the
     shared .report-row component; this just houses the expand-on-demand
     config fields + curve-preview SVG underneath it. */
  .sf-item-config{border:1px solid rgba(var(--overlay-rgb),0.08);border-top:none;border-radius:0 0 4px 4px;padding:8px 10px;margin:-4px 0 4px;display:flex;flex-direction:column;gap:6px;}

  /* Vault report — the interactive buys/taxes checklist. */
  .report-storefront{margin-bottom:14px;}
  .report-section-label{margin:8px 0 4px;}
  .report-row{display:flex;align-items:center;gap:10px;padding:6px 10px;background:rgba(var(--overlay-rgb),0.06);border-left:2px solid var(--border-strong);border-radius:0 3px 3px 0;margin-bottom:4px;flex-wrap:wrap;}
  .report-row.done{opacity:0.55;}
  .report-row.done .report-main{text-decoration:line-through;text-decoration-color:rgba(var(--overlay-rgb),0.35);}
  .report-check{width:16px;height:16px;border:1px solid rgba(var(--overlay-rgb),0.25);border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:10px;flex-shrink:0;color:var(--success);}
  .report-check.done{border-color:rgba(var(--success-rgb),0.5);background:rgba(var(--success-rgb),0.1);}
  .report-check.action{border-color:rgba(var(--warning-rgb),0.5);color:var(--warning);background:rgba(var(--warning-rgb),0.08);}
  .report-main{font-size:12px;color:var(--text);flex:1;min-width:180px;}
  .report-meta{font-size:11px;color:var(--text-dim);flex-shrink:0;}
  .report-actions{display:flex;gap:6px;align-items:center;flex-shrink:0;margin-left:auto;flex-wrap:wrap;}
  .report-totals{display:flex;flex-direction:column;gap:2px;font-size:11px;color:var(--text-secondary);padding:6px 10px;background:rgba(var(--accent-rgb),0.04);border-left:2px solid rgba(var(--accent-rgb),0.35);border-radius:0 3px 3px 0;}
  .report-totals-sum{color:var(--accent);letter-spacing:1px;margin-top:2px;}

  /* Shared layout primitives — a real tab bar, a capped-height scroll
     wrapper, and a "summoned, not permanent" collapsible section. Feature
     pages reuse these instead of hand-rolling their own tab/scroll/collapse
     markup. */

  /* Tab bar — container expects role="tablist"; each .tab a role="tab"
     with aria-selected set by the page's JS (CSS only provides the visual
     state via .on). Buttons are natively focusable/activatable; roving
     tabindex + arrow-key nav is the page JS's responsibility. */
  .tabbar{display:flex;gap:2px;border-bottom:1px solid rgba(var(--overlay-rgb),0.08);margin-bottom:10px;flex-wrap:wrap;}
  .tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--text-dim);font-family:inherit;font-size:11px;letter-spacing:1.5px;padding:8px 14px;cursor:pointer;transition:color .15s,border-color .15s;}
  .tab:hover{color:var(--text);}
  .tab.on{color:var(--accent);border-bottom-color:var(--accent);}
  .tab:focus-visible{outline:1px solid var(--accent);outline-offset:-1px;}
  .tabpanel{display:none;}
  .tabpanel.on{display:block;}

  /* Capped-height scroll wrapper — ~5 rows visible by default (pages may
     override max-height inline for a taller/shorter cap), own thin
     scrollbar, soft bottom fade so a cut-off row reads as "more below"
     rather than a hard clip.
     `overscroll-behavior:contain` matters here: once this container's own
     short scroll range is exhausted, the browser's default scroll CHAINING
     would otherwise hand a continuing wheel/touch gesture to the outer
     page instead of stopping — that reads as "the list won't scroll" even
     though the inner scroll itself works. `-webkit-overflow-scrolling:
     touch` is needed for momentum scrolling in pre-iOS 13 Safari, same fix
     applied to the mobile nav's horizontal scroller below. */
  .cap-scroll{position:relative;max-height:200px;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;}
  .cap-scroll::-webkit-scrollbar{width:4px;}
  .cap-scroll::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:2px;}
  /* Fades toward the current theme's own page tone (var(--bg-rgb)), not a
     literal black — a fixed black fade at the bottom of a scrollable list
     reads as a dark smudge on the light theme instead of a soft fade. */
  .cap-scroll::after{content:"";position:sticky;display:block;left:0;right:0;bottom:0;height:18px;margin-top:-18px;background:linear-gradient(rgba(var(--bg-rgb),0),rgba(var(--bg-rgb),0.9));pointer-events:none;}

  /* Collapsible "summoned, not permanent" section — closed by default,
     opens via its own header caret or an external trigger (e.g. a KPI
     tile click toggling .open). Amber-accented per the design mockups.
     Same solid-surface family as .panel/.home-card/.kpi-tile. Used as a
     top-level section, not nested inside another elevated surface, so this
     doesn't double up shadows anywhere. */
  .fold{background:var(--bg-panel);box-shadow:var(--shadow-tile);border:1px solid rgba(var(--overlay-rgb),0.08);border-radius:6px;margin-bottom:10px;overflow:hidden;}
  /* overflow:hidden was only meant to round .fold-head's background into
     the box's rounded corners, not to bound child popovers — an open
     .combo-list dropdown would otherwise get sliced off mid-row once it
     grew past the fold's bottom edge. Lifted while open; .fold-head
     self-rounds its top corners so it still reads correctly with the clip
     gone. */
  .fold.open{overflow:visible;}
  .fold-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 12px;cursor:pointer;user-select:none;font-size:11px;letter-spacing:1.5px;color:var(--warning);background:rgba(var(--warning-rgb),0.06);border-radius:5px 5px 0 0;}
  .fold-head:hover{background:rgba(var(--warning-rgb),0.1);}
  .fold-head i{transition:transform .15s;color:var(--warning);}
  .fold.open .fold-head i{transform:rotate(90deg);}
  .fold-body{display:none;padding:10px 12px;border-top:1px solid rgba(var(--overlay-rgb),0.08);}
  .fold.open .fold-body{display:block;}

  /* Dense data table — sortable headers, tight single-line rows. Base .tbl
     is the house table look (borders, spacing); .dense tightens padding
     further for pages with many rows (Inventory manifest, UEX feeds). */
  .tbl{width:100%;border-collapse:collapse;font-size:12px;}
  .tbl th{text-align:left;font-size:10px;letter-spacing:1.5px;color:var(--text-dim);font-weight:600;padding:6px 10px;border-bottom:1px solid rgba(var(--overlay-rgb),0.08);white-space:nowrap;}
  .tbl th.sortable{cursor:pointer;user-select:none;}
  .tbl th.sortable:hover{color:var(--text);}
  .tbl th.sorted{color:var(--accent);}
  .tbl td{padding:8px 10px;border-bottom:1px solid rgba(var(--overlay-rgb),0.04);vertical-align:middle;}
  .tbl tr:hover td{background:rgba(var(--overlay-rgb),0.02);}
  .tbl.dense th{padding:4px 8px;}
  .tbl.dense td{padding:5px 8px;font-size:11px;}

  /* Marketplace AVAILABLE TO ME's FOR SALE table — internally-scrolling
     with a sticky header and a vertical cap; without a cap, scrolling a
     long result list drags the whole page (topbar included) along with
     it. Same overscroll-behavior fix as .cap-scroll above, for the same
     scroll-chaining reason. */
  .market-table-scroll{max-height:520px;overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;}
  .market-table-scroll thead th{position:sticky;top:0;background:var(--bg-panel);z-index:1;}
  .market-table-scroll::-webkit-scrollbar{width:4px;}
  .market-table-scroll::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:2px;}

  /* confirmDialog must stack ABOVE the storefront/report modals (its
     overlay sits earlier in the DOM, and DOM order decides between equal
     z-indexes) — delete/remove confirms now fire from inside them. */
  #confirm-modal-overlay{z-index:1050;}

  .filter-chips{display:flex;flex-wrap:wrap;gap:4px;align-items:center;margin-top:8px;}
  .filter-chips:empty{display:none;}
  .filter-chip{background:rgba(var(--accent-rgb),0.12);border:1px solid rgba(var(--accent-rgb),0.4);color:var(--accent);font-size:11px;padding:4px 9px;border-radius:12px;cursor:pointer;font-family:inherit;letter-spacing:1px;}
  .filter-chip:hover{background:rgba(var(--danger-rgb),0.12);border-color:rgba(var(--danger-rgb),0.4);color:var(--danger-text-hover);}

  .type-badge{width:18px;height:18px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0;}
  /* Font Awesome <i> glyph — fixed width so every row icon lines up in a
     column regardless of the individual glyph's natural width. */
  .row-icon{flex-shrink:0;width:18px;font-size:16px;color:var(--accent);text-align:center;}
  .type-badge.mat{background:rgba(var(--accent-rgb),0.12);color:var(--accent);border:1px solid rgba(var(--accent-rgb),0.3);}
  .type-badge.comp{background:rgba(var(--component-rgb),0.12);color:var(--component);border:1px solid rgba(var(--component-rgb),0.3);}
  .type-badge.interest{background:rgba(var(--interest-rgb),0.12);color:var(--interest);border:1px solid rgba(var(--interest-rgb),0.3);}
  .type-badge.wanted{background:rgba(var(--accent-2-rgb),0.12);color:var(--accent-2);border:1px solid rgba(var(--accent-2-rgb),0.3);}

  .toolbar{display:flex;gap:4px;flex-wrap:wrap;margin:10px 0;align-items:center;}
  .tool-label{font-size:10px;color:var(--text-dim);letter-spacing:2px;margin-right:2px;}

  .item-list{display:flex;flex-direction:column;gap:4px;max-height:420px;overflow-y:auto;}
  .item-list::-webkit-scrollbar{width:4px;}
  .item-list::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:2px;}

  .item-row{display:flex;align-items:center;gap:12px;padding:11px 12px;background:rgba(var(--overlay-rgb),0.06);border-left:2px solid var(--border-strong);border-radius:0 3px 3px 0;}
  .item-row.sold{opacity:0.35;}
  .item-body{flex:1;min-width:0;}
  .item-mat{font-size:14px;font-weight:700;color:var(--text);margin-bottom:3px;}
  .item-qual{color:var(--accent);}
  .item-detail{font-size:12px;color:var(--text-tertiary);display:flex;gap:10px;flex-wrap:wrap;}
  .item-detail .hi{color:var(--text-secondary);}
  .item-ts{font-size:10px;color:var(--text-dim);margin-top:3px;}
  /* Fleet list's group tag + description — inline text, not a boxed form
     field, until hovered/focused (a bordered box on every row for text
     that's usually empty would be noisy). */
  .item-desc-input{background:none;border:1px solid transparent;padding:1px 4px;
    color:var(--text-tertiary);font-size:12px;width:120px;}
  .item-desc-input:hover,.item-desc-input:focus{border-color:rgba(var(--overlay-rgb),0.15);background:var(--bg-input);}
  .item-desc{display:block;margin-top:4px;width:100%;font-style:italic;}
  .fleet-group-rename-input{max-width:180px;margin-left:8px;}
  .market-pending-mobile{display:none;}
  .chat-row-mobile{display:none;}
  /* .chat-thread-row is a column of [.chat-row-top, the mobile-only
     full-width detail line] instead of one flat row. On desktop the mobile
     line stays hidden, so this looks identical to a plain .item-row; see
     the mobile block below for why the split exists. */
  .item-row.chat-thread-row{flex-direction:column;align-items:stretch;gap:6px;}
  .chat-row-top{display:flex;align-items:center;gap:12px;}

  /* Chat message bubbles (#chat-messages, shared by all four thread kinds) */
  .chat-message-row{display:flex;flex-direction:column;align-items:flex-start;margin-bottom:8px;}
  .chat-message-row.mine{align-items:flex-end;}
  .chat-bubble{max-width:80%;padding:8px 12px;border-radius:8px;background:rgba(var(--overlay-rgb),0.05);border:1px solid rgba(var(--overlay-rgb),0.08);}
  .chat-bubble.mine{background:rgba(var(--accent-rgb),0.12);border-color:rgba(var(--accent-rgb),0.3);}
  .chat-bubble-sender{margin-bottom:2px;}
  .chat-bubble-body{color:var(--text);font-size:12px;}

  /* .inv-row-badge/.inv-row-info — the icon slot and the chip row (LISTED/
     IN DEAL/CRAFTED/owned-held) — are shared by marketplace.js's own
     .item-row rows and the Inventory manifest table's icon/STATUS cells.
     Inventory is a single-line .tbl.dense manifest. */
  .inv-row-badge{display:flex;align-items:center;flex-shrink:0;}
  .inv-row-info{flex:1;min-width:0;display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:11px;color:var(--text-tertiary);}
  .inv-row-info:empty{display:none;}
  /* Hidden by default (desktop shows the real col-location/col-status
     columns instead) — shown only on mobile, see the max-width:750px block
     below. */
  .inv-row-secondary{display:none;font-size:11px;color:var(--text-dim);margin-top:2px;}
  .row-actions-inline{display:flex;gap:2px;align-items:center;justify-content:flex-end;}

  /* Per-row "⋯" overflow menu — the rarer row actions live here; the core
     three (List/Edit/Delete) are always-visible icon buttons instead.
     position:fixed with top/left computed in JS (dashboard.js
     toggleRowMenu), NOT position:absolute — the row lives inside a
     scrolling container that clips any absolutely-positioned descendant
     extending past its own bounds. Toggled via delegated class rather than
     a fixed id, since rows re-render via innerHTML constantly. z-index
     above #info-hover-tooltip since it's interactive, not just
     informational. */
  .row-menu{position:relative;}
  .row-menu-dropdown{display:none;position:fixed;min-width:180px;background:var(--bg-dropdown);border:1px solid rgba(var(--overlay-rgb),0.1);border-radius:6px;box-shadow:var(--shadow-dropdown);z-index:2100;overflow:hidden;}
  .row-menu-dropdown.open{display:block;}
  .row-menu-item{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;border:none;color:var(--text-dim);font-family:inherit;font-size:11px;letter-spacing:0.5px;padding:9px 12px;cursor:pointer;}
  .row-menu-item:hover{background:rgba(var(--accent-rgb),0.08);color:var(--accent);}
  .row-menu-item.danger:hover{background:rgba(var(--danger-rgb),0.08);color:var(--danger-text-hover);}

  /* Always-on hover info (js/hover-tip.js) — visually identical to
     #help-hover-tooltip but NOT gated behind Help Mode; any [data-tip]
     element shows its tip on hover regardless of that toggle.
     white-space:pre-line so a multi-stat breakdown reads one line per stat
     instead of one run-on line. */
  #info-hover-tooltip{position:fixed;z-index:2000;max-width:320px;display:none;
    background:rgba(10,20,28,0.97);border:1px solid rgba(var(--accent-rgb),0.4);
    border-radius:6px;padding:10px 12px;font-size:12px;line-height:1.7;
    color:rgba(255,255,255,0.92);pointer-events:none;white-space:pre-line;
    box-shadow:0 4px 16px rgba(0,0,0,0.5);}
  #info-hover-tooltip.open{display:block;}
  [data-tip]{cursor:help;}
  /* Status pill: a colored info badge (color set inline per status), not a
     .btn variant, but its metrics still follow the same --btn-* tokens so it
     reads as "the same size unit" as the buttons next to it. */
  .status-btn{font-size:var(--btn-font-sm);letter-spacing:var(--btn-letter);padding:6px 10px;border-radius:var(--btn-radius-icon);border:1px solid;white-space:nowrap;flex-shrink:0;cursor:pointer;font-family:inherit;background:none;}
  .status-btn:hover{filter:brightness(1.3);}
  .listed-chip{font-size:10px;color:var(--accent-2);background:rgba(var(--accent-2-rgb),0.08);border:1px solid rgba(var(--accent-2-rgb),0.3);border-radius:10px;padding:2px 8px;letter-spacing:1px;vertical-align:middle;white-space:nowrap;}
  /* Semantic status-chip colors — shared across several render sites
     instead of duplicated inline style="". Same palette as .txn-step. */
  .chip-green{color:var(--success);border-color:rgba(var(--success-rgb),0.33);background:rgba(var(--success-rgb),0.08);}
  .chip-amber{color:var(--warning);border-color:rgba(var(--warning-rgb),0.33);background:rgba(var(--warning-rgb),0.08);}
  .chip-danger{color:var(--danger-text);border-color:rgba(var(--danger-rgb),0.33);background:rgba(var(--danger-rgb),0.08);}
  .chip-muted{color:var(--text-dim);border-color:rgba(var(--muted-rgb),0.33);background:transparent;}
  /* Kleinanzeigen-style transaction step tracker (chat modal) */
  .txn-steps{display:flex;align-items:center;flex-wrap:wrap;gap:4px;margin-bottom:10px;font-size:10px;letter-spacing:0.5px;}
  .txn-step{padding:3px 8px;border-radius:10px;border:1px solid;white-space:nowrap;}
  .txn-step.done{color:var(--success);border-color:rgba(var(--success-rgb),0.33);background:rgba(var(--success-rgb),0.08);}
  .txn-step.current{color:var(--warning);border-color:rgba(var(--warning-rgb),0.33);background:rgba(var(--warning-rgb),0.08);}
  .txn-step.pending{color:var(--text-dim);border-color:rgba(var(--muted-rgb),0.33);background:transparent;}
  .txn-connector{color:var(--text-dim);font-size:11px;}
  /* Marketplace "Twin Manifest" — the big bordered tab-style header for the
     AVAILABLE TO ME panel. MY LISTINGS sits on a plain .fold below it,
     reusing .fold's standard styling like every other collapsible
     section, rather than a separate slide-in drawer. */
  .col-tab-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 16px;border:1px solid rgba(var(--accent-rgb),0.35);border-bottom:2px solid var(--accent);border-radius:8px 8px 0 0;background:rgba(var(--accent-rgb),0.06);}
  .col-tab-title{font-size:13px;font-weight:700;letter-spacing:2px;color:var(--accent);}

  /* UEX Market "Command Deck: Alerts + Arbitrage" — the free-for-everyone
     sell-through ticker (horizontally scrolling, unlike .toolbar which only
     scrolls on mobile) and the ALERTS+ARBITRAGE side-by-side duo,
     premium-only. */
  .uex-ticker{display:flex;gap:14px;align-items:center;overflow-x:auto;white-space:nowrap;padding-bottom:6px;margin-bottom:10px;}
  .uex-ticker::-webkit-scrollbar{height:4px;}
  .uex-ticker::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:2px;}
  .uex-duo{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start;}
  @media (max-width:900px){.uex-duo{grid-template-columns:1fr;}}

  /* Event Vault detail page: ONE grid holding every panel — header,
     WANTED/CONTRIBUTED, REWARDS/LOTTERY PARTICIPANTS, REWARD LOTTERY —
     instead of several separately-stacked duo/solo blocks. Panels marked
     .span-2 (header, REWARD LOTTERY) run the full width; everything else
     is a normal 2-up grid cell. Doing this as one grid keeps the column
     edges aligned top-to-bottom and collapses to a single column as one
     unit on mobile, instead of each block drifting out of alignment.
     align-items is left at the grid default (stretch), NOT `start` like
     .craft-plan-duo/.uex-duo — those pair columns of naturally different
     length by design, but here a header that wraps to two lines must not
     leave its shorter row-mate looking cut short with a dead gap. */
  .event-vault-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
  .event-vault-grid .span-2{grid-column:1 / -1;}
  @media (max-width:900px){.event-vault-grid{grid-template-columns:1fr;}}

  /* Full-width banner strip on the event detail page, sitting between the
     panel-header (event name) and the panel-body — the existing 32px
     header thumbnail and 40px dashboard-list thumbnail stay as they are,
     this is an additional, more prominent placement. object-position is
     set inline per-event from banner_focal_x/y so the uploader's chosen
     focal point survives the fixed-aspect crop. */
  .event-vault-banner-strip{width:100%;height:240px;object-fit:cover;display:block;border-bottom:1px solid var(--border-strong);}
  @media (max-width:600px){.event-vault-banner-strip{height:160px;}}

  /* Focal-point picker in the edit modal: the full (uncropped) image at its
     natural aspect ratio, click anywhere to set what the banner strip's
     fixed-aspect crop centers on. width:100%/height:auto (no object-fit)
     keeps the rendered box exactly matching the image so a click's pixel
     offset maps 1:1 to an image fraction — no letterboxing math needed. */
  .event-vault-banner-focal-wrap{position:relative;cursor:crosshair;line-height:0;border-radius:6px;overflow:hidden;}
  .event-vault-banner-focal-wrap img{width:100%;height:auto;display:block;}
  .event-vault-banner-focal-marker{position:absolute;width:16px;height:16px;border:2px solid #fff;border-radius:50%;box-shadow:0 0 0 2px rgba(0,0,0,0.6);transform:translate(-50%,-50%);pointer-events:none;}

  .empty-state{text-align:center;padding:30px 16px;color:var(--text-dim);font-size:11px;letter-spacing:2px;}
  .empty-state-hint{margin-top:8px;font-size:11px;letter-spacing:0.5px;color:var(--text-dim);max-width:340px;margin-left:auto;margin-right:auto;line-height:1.5;}

  .login-gate{text-align:center;padding:40px 20px;color:var(--text-dim);font-size:11px;letter-spacing:1px;}
  .login-gate .spinner{width:20px;height:20px;border:2px solid var(--border-strong);border-top-color:var(--accent);border-radius:50%;margin:0 auto 14px;animation:spin .8s linear infinite;}
  @keyframes spin{to{transform:rotate(360deg);}}

  /* Discord brand button: deliberate exception to the app's button palette
     — third-party auth buttons use the provider's own brand color, see
     STYLE_GUIDE.md. Box model still comes from .btn; only color/radius/size
     are brand-specific overrides. */
  .discord-btn{background:#5865F2;color:#fff;border-color:#5865F2;padding:11px 22px;border-radius:6px;font-size:12px;font-weight:700;margin-top:16px;}
  .discord-btn:hover{background:#4752C4;border-color:#4752C4;}

  /* Logged-out legal block under the login card: the CIG fansite
     disclaimer (required by CIG's Fan Content policy, must be visible
     without login) + the About/Data modal entry point. */
  .login-legal{max-width:520px;margin:40px auto 0;padding:0 12px;text-align:center;
    font-size:10px;line-height:1.6;color:var(--text-dim);letter-spacing:0.4px;}
  .login-legal p{margin:0 0 6px;}
  .login-legal .link-btn{font-size:10px;color:var(--text-dim);}

  /* Sidebar footer entry point to the same modal for logged-in users. */
  .sidebar-legal-link{margin-top:auto;background:none;border:none;cursor:pointer;
    font-family:inherit;font-size:9px;letter-spacing:1.5px;color:var(--text-dim);
    padding:14px 10px;text-align:left;}
  .sidebar-legal-link:hover{color:var(--text-dim);}

  /* About/Data modal body — the only long-form prose surface in the app. */
  .legal-body{font-size:12px;line-height:1.65;color:var(--text-secondary);}
  .legal-body h3{font-size:11px;letter-spacing:2px;color:var(--text-dim);margin:16px 0 6px;}
  .legal-body h3:first-child{margin-top:0;}
  .legal-body p{margin:0 0 8px;}
  .legal-body ul{margin:0 0 8px;padding-left:18px;}
  .legal-body li{margin-bottom:5px;}
  .legal-body a{color:var(--accent);}

  .user-bar{display:flex;align-items:center;gap:8px;font-size:10px;color:var(--text-tertiary);letter-spacing:1px;}
  .user-bar img{width:18px;height:18px;border-radius:50%;}
  /* Global refresh indicator: "SYNC 14:02 · 3m ago" + "next in 4:12",
     updated by a 1s textContent-only ticker. Only visible on the UEX
     Market page. */
  .refresh-status{display:flex;flex-direction:column;align-items:flex-end;gap:1px;
    font-size:9px;color:var(--text-dim);letter-spacing:0.5px;white-space:nowrap;margin-right:4px;}
  /* Generic avatar for accounts without OAuth metadata (email/magic-link) */
  .user-avatar-fallback{width:18px;height:18px;border-radius:50%;background:rgba(var(--overlay-rgb),0.08);
    border:1px solid rgba(var(--overlay-rgb),0.15);display:inline-flex;align-items:center;justify-content:center;
    font-size:9px;color:var(--text-tertiary);flex-shrink:0;}
  .user-menu{position:relative;}
  .user-menu-btn{display:flex;align-items:center;gap:8px;background:none;border:none;color:var(--text-tertiary);font-family:inherit;font-size:10px;letter-spacing:1px;cursor:pointer;padding:4px;}
  .user-menu-btn:hover{color:var(--text);}
  .user-menu-dropdown{display:none;position:absolute;top:100%;right:0;margin-top:6px;min-width:160px;background:var(--bg-dropdown);border:1px solid rgba(var(--overlay-rgb),0.1);border-radius:6px;box-shadow:var(--shadow-dropdown);z-index:1100;overflow:hidden;}
  .user-menu-dropdown.open{display:block;}
  .user-menu-item{display:block;width:100%;text-align:left;background:none;border:none;color:var(--text-dim);font-family:inherit;font-size:11px;letter-spacing:1px;padding:10px 14px;cursor:pointer;}
  .user-menu-item:hover{background:rgba(var(--accent-rgb),0.08);color:var(--accent);}

  .error-box{background:rgba(var(--danger-rgb),0.08);border:1px solid rgba(var(--danger-rgb),0.3);color:var(--danger-text-hover);padding:12px;border-radius:4px;font-size:11px;letter-spacing:1px;text-align:center;}

  /* visibility (not display) drives show/hide, so opacity/transform can
     actually transition (display can't be animated). visibility's own
     transition is delayed on the way in / undelayed on the way out, the
     standard trick to keep it out of the tab order and a11y tree while
     hidden without killing the fade. */
  .modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:1000;padding:20px;
    opacity:0;visibility:hidden;transition:opacity .15s ease,visibility 0s linear .15s;}
  .modal-overlay.open{opacity:1;visibility:visible;transition:opacity .15s ease,visibility 0s linear 0s;}
  /* Center modals within the WORKSPACE, not the viewport: the scrim still
     covers everything (inset:0), but on desktop the flex centering is
     shifted right by the sidebar width so the box lands in the middle of
     the main working area. Reverts automatically under the 900px
     breakpoint where the sidebar is an off-canvas drawer. 901px scaled down
     to 751px (÷1.2) to match html's zoom:1.2. */
  @media (min-width:751px){ .modal-overlay{padding-left:calc(200px + 20px);} }
  /* Fixed-size modal, same footprint for every entry type — nothing may
     "grow" into view mid-edit, one constant size, never animated/
     transitioned by content. */
  /* max-height is 75vh, not 90vh: vh resolves against the real, unzoomed
     viewport, but html's global zoom:1.2 then renders the box 1.2x larger
     on screen — 90vh would paint at ~108% of the actual browser height,
     pushing tall modals above the top of the window. 75 = 90 / 1.2
     cancels that out. */
  .modal-box{width:100%;max-height:75vh;overflow-y:auto;background:var(--bg-panel);border:1px solid rgba(var(--overlay-rgb),0.1);border-radius:8px;box-shadow:var(--shadow-modal);
    max-width:clamp(300px,94vw,940px);transform:scale(0.97);transition:transform .15s ease;}
  .modal-overlay.open .modal-box{transform:scale(1);}
  /* Composable "roomy" modal sizing — width and height are independent so a
     modal that's already wide enough can opt into just the height
     treatment, and a modal with often-thin content can opt into a height
     CAP instead of a forced height. */
  .modal-box--w-roomy{max-width:clamp(300px,94vw,640px);}
  /* Forces the box to 70vh even when content is short — only for modals
     whose content is reliably substantial. flex+overflow:hidden here (and
     modal-body flex:1) pins the header/footer at the true top/bottom of a
     FIXED-height box; without it, position:sticky alone floats the footer
     mid-box when content doesn't fill the height. Only needed for a fixed
     height — a content-driven max-height never triggers it. */
  .modal-box--h-fixed{height:70vh;display:flex;flex-direction:column;overflow:hidden;}
  .modal-box--h-fixed .modal-body{flex:1;min-height:0;overflow-y:auto;}
  /* Caps at 70vh but shrinks for lighter content — no forced empty space
     below a short form/list, no sticky-footer-floats-mid-box bug either. */
  .modal-box--h-fluid{max-height:70vh;}
  /* Entry modal specifically (id, not the shared .modal-box class used by
     every other modal) gets an explicit 940x877 target — not just capped,
     truly fixed at that size on any screen big enough for it. max-width/
     max-height remain as safety nets so it still shrinks gracefully on a
     smaller viewport. display:flex column + overflow:hidden (overriding
     the generic .modal-box's own overflow-y:auto) pins the header/footer
     to the true top/bottom of the fixed 877px box — position:sticky alone
     only "sticks" once scrolling would otherwise carry them out of view,
     so on short content they'd sit wherever the content ended instead of
     anchored at the bottom. Only #modal-box's own .modal-body scrolls;
     header/footer are flex children that never move.
     max-height:75vh (=90/1.2) cancels html's global zoom:1.2 the same way
     the generic .modal-box above does. */
  #modal-box{width:940px;height:877px;max-width:94vw;max-height:75vh;display:flex;flex-direction:column;overflow:hidden;}
  #modal-box .modal-body{flex:1;min-height:0;overflow-y:auto;}
  /* Widened while refinery-scan mode is active — the crop box needs real
     room to drag/resize precisely, and the side-col it lives in was too
     cramped at the standard 940px width. Toggled by ocr-refinery-scan.js's
     startRefineryScan/stopRefineryScan. .fieldset's 1fr/1.2fr grid columns
     (below) scale with this, so both the form fields and the side-col
     (where the scan UI lives) get proportionally more width, not just the
     side-col alone. A genuinely separate modal was considered instead but
     skipped — it would need forking the batch-review render/save logic,
     which isn't parameterized by container id (see ocr-refinery-scan.js's
     file header for the fuller tradeoff). */
  #modal-box.modal-box--refinery-scan{width:1400px;max-width:96vw;}
  /* Want-Ad modal — same fixed-size/pinned-header-footer treatment as
     #modal-box above, just a separate id since #modal-box is already
     taken. max-height:75vh (=90/1.2) — same zoom:1.2 compensation. */
  #wantad-modal-box{width:940px;height:877px;max-width:94vw;max-height:75vh;display:flex;flex-direction:column;overflow:hidden;}
  #wantad-modal-box .modal-body{flex:1;min-height:0;overflow-y:auto;}
  .vault-modal-divider{border-top:1px solid rgba(var(--overlay-rgb),0.08);margin:4px 0;}
  .modal-header{padding:14px 18px;border-bottom:1px solid var(--border-strong);display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;background:var(--bg-panel);}
  .modal-title{font-family:'Orbitron',monospace;font-size:12px;letter-spacing:2px;color:var(--accent);}
  .modal-close{font-size:18px;}
  .modal-close:hover{color:var(--danger);}
  .logout-btn:hover{color:var(--danger);border-color:rgba(var(--danger-rgb),0.4);}
  .modal-body{padding:16px 18px;display:flex;flex-direction:column;gap:12px;}

  /* Centered segmented control, not stretched edge-to-edge — the modal is
     now always wide, so a full-width Material/Component toggle would look
     like two oversized tabs rather than a compact switch. */
  .type-toggle{display:flex;justify-content:center;gap:6px;margin-bottom:4px;}
  .type-toggle button{flex:0 0 auto;min-width:140px;}
  /* Compact variant for a toggle with more than 2-3 short-label buttons
     (e.g. the fleet-build picker's ALL/GRADE A-D chips) — the default
     140px min-width assumes 2-3 wide labels and overflows with 5 short
     ones, clipping the last button(s) instead of wrapping. */
  .type-toggle--compact{justify-content:flex-start;flex-wrap:wrap;}
  .type-toggle--compact button{min-width:0;flex:0 0 auto;}

  .field{display:flex;flex-direction:column;gap:4px;}
  .field label{font-size:10px;letter-spacing:1px;color:var(--text-dim);}
  /* Global, NOT scoped to .field — any input/select/textarea anywhere in
     the app should match the dark theme by default, not just ones sitting
     inside the entry-modal .field wrapper; scoping to .field only leaves
     inputs outside it falling back to unstyled browser defaults.
     Checkbox/radio/range keep native rendering (radio has its own look via
     .radio-row, range has its own custom thumb styling below). */
  input:not([type=checkbox]):not([type=radio]):not([type=range]),select,textarea{background:var(--bg-input);border:1px solid rgba(var(--overlay-rgb),0.1);border-radius:4px;padding:8px 10px;color:var(--text);font-family:inherit;font-size:12px;}
  textarea{resize:vertical;}
  input:not([type=checkbox]):not([type=radio]):not([type=range]):focus,select:focus,textarea:focus{outline:none;border-color:var(--accent);}
  .field-row{display:flex;gap:10px;}
  .field-row .field{min-width:0;}
  /* Default stretch made fields with a shorter label sit their input
     higher than a neighbor whose label wrapped to 2 lines — every
     field-row-compact usage aligns inputs to a shared bottom edge
     regardless of label length, instead of relying on each call site to
     remember its own align-items:flex-end. */
  .field-row-compact{align-items:flex-end;}
  /* Generic wrapped inline form-control row — replaces one-off
     style="display:flex;gap:6px;flex-wrap:wrap;align-items:center" repeats,
     e.g. User Management's per-user action rows. */
  .frow{display:flex;gap:6px;flex-wrap:wrap;align-items:center;}
  /* Native number spinners look out of place against the dark theme. */
  input[type=number]::-webkit-inner-spin-button,
  input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0;}
  input[type=number]{-moz-appearance:textfield;}

  .combo{position:relative;}
  /* .combo itself isn't a flex container, so its <input> never inherited
     .field's flex-stretch — it fell back to the browser's intrinsic ~170px
     text-input width, while .combo-list (anchored via left:0;right:0 to
     .combo, which IS stretched by .field) rendered full-width. Fixed at
     the shared rule, not per-field. */
  .combo input{width:100%;padding-right:26px;}
  /* Decorative down-chevron — a native <select> draws its own OS arrow;
     this plain text <input> had none, so it didn't read as an expandable
     picker even though both share identical box-model CSS.
     pointer-events:none so it never blocks clicks/typing into the input
     underneath. */
  .combo::after{content:"";position:absolute;right:10px;top:50%;transform:translateY(-2px);width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:5px solid var(--text-dim);pointer-events:none;}
  .combo-list{position:absolute;top:calc(100% + 4px);left:0;right:0;max-height:180px;overflow-y:auto;background:var(--bg-input);border:1px solid rgba(var(--accent-rgb),0.25);border-radius:4px;z-index:20;display:none;box-shadow:var(--shadow-dropdown);}
  .combo-list.open{display:block;}
  .combo-option{padding:8px 10px;font-size:12px;color:var(--text-secondary);cursor:pointer;}
  .combo-option:hover,.combo-option.active{background:rgba(var(--accent-rgb),0.12);color:var(--accent);}
  /* Number inputs stretched to fill an arbitrary flex column look
     oversized for what's usually 1-4 digits — fixed at the shared rule
     rather than per-field, same reasoning as .combo input above. Scoped to
     .field specifically (not a blanket input[type=number]) so a number
     input rendered outside that layout isn't affected by a cap it was
     never stretched into needing. */
  .field input[type=number]{max-width:120px;}
  /* Crafting Plan's own three quantity/quality number inputs carry this
     class explicitly so they render at one consistent width regardless of
     whether a given input sits inside a .field wrapper (the plan-list one
     doesn't — .item-row, not a form). Takes priority over the .field rule
     above via equal specificity + source order. */
  .craft-plan-num-input{max-width:90px;}
  /* Overrides field-row-compact's flex-end (shared bottom edge) with
     flex-start for the crafting-plan add-form's per-slot row specifically —
     PREVIEW's stat lines can wrap to more than one line, and bottom-
     aligning would drag MATERIAL/QUALITY's single-line labels down out of
     sync with it instead of keeping all three level at the top. */
  .field-row-compact.craft-plan-slot-row{align-items:flex-start;}
  /* Plan-list QTY only — narrower than the shared 90px above; comes after
     it in source order so it wins on the plan-list row's input, which
     carries both classes. */
  .craft-plan-row-qty{max-width:44px;}
  /* Crafting Plan's add-form TYPE toggle sits in a much narrower column
     than .type-toggle's other current use (a full-width modal) — that
     context's min-width:140px per button badly crowded this column.
     flex:1 guarantees all three buttons render at the exact same pixel
     width, filling the column evenly instead. */
  .craft-plan-type-toggle button{flex:1;min-width:0;}
  /* Top-bar theme quick menu — same problem as .craft-plan-type-toggle
     above: a 230px dropdown can't fit .type-toggle's default
     min-width:140px per button, let alone the FONT SIZE row's four
     buttons. flex:1 fills the dropdown width evenly instead. */
  .quick-menu-type-toggle{justify-content:stretch;}
  .quick-menu-type-toggle button{flex:1;min-width:0;}
  /* WHERE TO MINE's system selector — an arbitrary, growing number of
     systems (not a fixed 2-3 like the TYPE toggle above), inline inside a
     table detail row rather than a full column, so it wraps and each
     button sizes to its own label instead of forcing equal widths. */
  .craft-plan-mining-system-toggle{flex-wrap:wrap;justify-content:flex-start;}
  .craft-plan-mining-system-toggle button{flex:0 0 auto;min-width:0;}
  /* Inventory page's Holder filter — same problem as the mining-system
     toggle above: an arbitrary, growing number of vault members sitting in
     a left-aligned toolbar row, not a fixed-width modal column. */
  .type-toggle.inv-holder-toggle{justify-content:flex-start;flex-wrap:wrap;}
  .type-toggle.inv-holder-toggle button{min-width:0;flex:0 0 auto;}

  .radio-row{display:flex;gap:12px;}
  .radio-row label{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-secondary);cursor:pointer;}

  .chip-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:2px;}
  .chip{background:rgba(var(--accent-rgb),0.08);border:1px solid rgba(var(--accent-rgb),0.25);color:var(--chip-text);font-size:11px;padding:4px 9px;border-radius:12px;cursor:pointer;font-family:inherit;}
  .chip:hover{background:rgba(var(--accent-rgb),0.18);color:var(--accent);}
  .chip.active{background:var(--accent);border-color:var(--accent);color:var(--on-accent);font-weight:700;}
  .chip-note{font-size:11px;color:var(--text-dim);margin-top:2px;}
  .chip-note-warn{color:var(--warning);margin-bottom:6px;}

  /* Component performance-stats table (craftable.js's componentStatsTableHtml())
     — a compact label/value pair grid, not a bordered data table; shared by
     any place a specific component's full stats get shown (today: the
     fleet-build sticky sidebar). */
  .component-stats-table{width:100%;border-collapse:collapse;margin-top:8px;font-size:11px;}
  .component-stats-table td{padding:3px 0;border-bottom:1px solid rgba(var(--overlay-rgb,255,255,255),0.06);}
  .component-stats-table td:first-child{color:var(--text-dim);}
  .component-stats-table td:last-child{text-align:right;color:var(--text);font-weight:600;}
  .component-stats-table tr:last-child td{border-bottom:0;}

  /* Group Management "custody" tab settings — icon + label + control +
     one-line hint per card, instead of a bare label/select/paragraph stack.
     Reused as `.setting-card` outside Group Management (Account page,
     storefront modal) — same rules, name doesn't imply "vault group manage". */
  .vgm-setting-card, .setting-card{display:flex;gap:10px;padding:10px 12px;background:rgba(var(--overlay-rgb),0.06);border:1px solid rgba(var(--overlay-rgb),0.08);border-radius:6px;}
  .vgm-setting-card+.vgm-setting-card, .setting-card+.setting-card{margin-top:8px;}
  .vgm-setting-card>i, .setting-card>i{font-size:14px;color:var(--accent);flex-shrink:0;margin-top:2px;}
  .vgm-setting-card-body, .setting-card-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px;}
  .vgm-setting-card-label, .setting-card-label{font-size:12px;font-weight:700;letter-spacing:0.5px;color:var(--text);}
  .vgm-setting-card-hint, .setting-card-hint{font-size:11px;color:var(--text-dim);}

  /* Vault roster presence dot (js/vault-management.js) — live, not
     persisted; gray by default, green while a Realtime Presence sync says
     that member is on this vault's members tab right now. */
  .presence-dot{width:8px;height:8px;border-radius:50%;background:#7A6CA0;flex:none;}
  .presence-dot.presence-online{background:#4ADE80;box-shadow:0 0 4px rgba(74,222,128,0.6);}

  /* Community item photos (js/item-images.js) — small square thumb, used in
     both the admin curation list and inline next to items once approved. */
  .item-image-thumb{width:32px;height:32px;object-fit:cover;border-radius:4px;border:1px solid rgba(var(--accent-rgb),0.25);flex:none;}

  .slot-row{background:rgba(var(--accent-rgb),0.04);border:1px solid rgba(var(--accent-rgb),0.15);border-radius:4px;padding:8px 10px;margin-bottom:8px;}
  .slot-row label{color:var(--accent);}
  .slot-row .slot-material{margin-bottom:6px;}
  .slot-meta{font-size:11px;color:var(--text-tertiary);margin-bottom:6px;}
  .slot-result{font-size:11px;color:var(--text-secondary);margin-top:4px;min-height:14px;}
  .slot-result span{color:var(--component);margin-right:8px;}

  .slider-row{display:flex;align-items:center;gap:8px;}
  .slider-row input[type=range]{flex:1;-webkit-appearance:none;appearance:none;height:4px;border-radius:2px;background:rgba(var(--overlay-rgb),0.1);outline:none;}
  .slider-row input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;border-radius:50%;background:var(--accent);cursor:pointer;box-shadow:0 0 6px rgba(var(--accent-rgb),0.6);}
  .slider-row input[type=range]::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--accent);cursor:pointer;border:none;box-shadow:0 0 6px rgba(var(--accent-rgb),0.6);}
  .slider-value{font-size:11px;color:var(--text);width:36px;text-align:right;flex-shrink:0;}
  .slider-bounds{display:flex;justify-content:space-between;font-size:10px;color:var(--text-dim);margin-top:2px;}

  .blueprint-header{display:flex;gap:16px;flex-wrap:wrap;padding:8px 10px;background:rgba(var(--overlay-rgb),0.06);border-radius:4px;margin-bottom:10px;font-size:11px;color:var(--text-secondary);}
  .blueprint-header b{color:var(--accent);}

  /* Craft mode (entry modal): RECORD/CRAFT toggle + per-slot source picker */
  .craft-mode-toggle{justify-content:flex-start;margin-bottom:0;}
  .craft-mode-toggle button{min-width:100px;}
  .craft-src-list{display:flex;flex-direction:column;gap:4px;}
  .craft-src-row{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--text-secondary);padding:3px 6px;border:1px solid rgba(var(--overlay-rgb),0.06);border-radius:4px;}
  .craft-src-row input[type=checkbox]{flex-shrink:0;accent-color:var(--accent);}
  .craft-src-row.disabled{opacity:0.45;}
  .craft-src-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .craft-src-label b{color:var(--text);}
  .craft-src-vault{color:var(--text-dim);font-size:10px;letter-spacing:1px;}
  .craft-src-owner{color:var(--warning);font-size:10px;}
  .craft-src-row input[type=number]{width:74px;flex-shrink:0;padding:2px 6px;font-size:11px;}
  .craft-coverage{font-size:11px;margin-top:6px;padding-top:6px;border-top:1px solid rgba(var(--overlay-rgb),0.08);color:var(--text-tertiary);}
  .craft-coverage.ok{color:var(--success);}
  .craft-coverage.short{color:var(--warning);}
  .craft-coverage b{color:inherit;}
  .craft-manual-row{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-tertiary);margin-top:8px;cursor:pointer;}
  .craft-manual-row input[type=checkbox]{accent-color:var(--component);}
  .craft-onhand-row{font-size:11px;color:var(--text-tertiary);margin-top:6px;display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
  .craft-onhand-row input[type=number]{width:80px;padding:2px 6px;font-size:11px;}
  .crafted-chip{font-size:9px;letter-spacing:1px;color:var(--component);border:1px solid rgba(var(--component-rgb),0.4);background:rgba(var(--component-rgb),0.08);border-radius:10px;padding:1px 7px;vertical-align:middle;}
  .crafted-inputs-note{font-size:11px;color:var(--text-tertiary);margin-bottom:8px;}

  .baseline-line{font-size:11px;color:var(--text-secondary);}

  .uex-avg{font-size:13px;color:var(--text);margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid rgba(var(--overlay-rgb),0.06);}
  .uex-avg b{color:var(--accent-2);font-size:16px;}
  .uex-system{margin-bottom:8px;}
  .uex-system-name{font-size:10px;letter-spacing:2px;color:var(--text-dim);margin-bottom:3px;}
  .uex-row{display:flex;justify-content:space-between;font-size:11px;color:var(--text-secondary);padding:2px 0;}
  .uex-row span:last-child{color:var(--text);font-weight:700;}
  .uex-row.uex-empty{color:var(--text-dim);font-style:italic;}
  /* LISTINGS tab rows: a leading WTS/WTB chip + title on the first line,
     quality/price/age dropped to their own dim line below instead of one
     long dot-joined string. align-items:flex-start (.summary-row is
     centered by default) so the chip sits at the title's baseline instead
     of centered against the two-line block. */
  .uex-listing-row{align-items:flex-start;}
  .uex-op-chip{flex-shrink:0;margin-top:1px;}
  .uex-op-chip.uex-op-buy{color:var(--buy);background:rgba(var(--buy-rgb),0.08);border-color:rgba(var(--buy-rgb),0.3);}

  #blueprint-summary{overflow-x:auto;}
  .summary-table{width:100%;border-collapse:collapse;font-size:11px;margin-top:10px;}
  .summary-table th{text-align:left;color:var(--text-dim);letter-spacing:1px;font-weight:400;padding:4px 6px;border-bottom:1px solid var(--border-strong);}
  .summary-table td{padding:4px 6px;color:var(--text);border-bottom:1px solid rgba(var(--overlay-rgb),0.05);}

  .fieldset{display:grid;grid-template-columns:1fr 1.2fr;gap:16px;align-items:stretch;}
  .hidden{display:none;}
  .fs-col{display:flex;flex-direction:column;gap:12px;min-width:0;min-height:0;}
  .side-col{background:rgba(var(--overlay-rgb),0.06);border:1px solid rgba(var(--overlay-rgb),0.06);border-radius:6px;padding:12px;min-height:340px;overflow-y:auto;}
  .side-placeholder{font-size:12px;color:var(--text-dim);letter-spacing:1px;padding:20px 6px;text-align:center;}
  /* In the entry modal, the fieldset (and therefore its side-col — the
     material batch list / component blueprint panel) stretches to fill
     the modal-body's remaining fixed-877px height, instead of only being
     as tall as its own content — this lets the batch list show a long
     list of staged items at full modal height. */
  #modal-box #fieldset-material,#modal-box #fieldset-component{flex:1;min-height:0;}
  /* 720px scaled down to 600px (÷1.2) to match html's zoom:1.2. */
  @media (max-width:600px){
    .fieldset{grid-template-columns:1fr;}
    .side-col{min-height:0;}
    /* The desktop fill-the-modal height constraint (flex:1 above) squeezes
       the stacked single-column grid rows into each other once the craft
       source picker makes the columns tall — the blueprint side-col would
       then paint over Quantity/Location/Vault. Content-size the fieldset
       instead; .modal-body scrolls. */
    #modal-box #fieldset-material,#modal-box #fieldset-component{flex:none;min-height:auto;}
  }

  /* Sidebar becomes an off-canvas drawer under 900px (scaled down to 750px,
     ÷1.2, to match html's zoom:1.2) — a fixed-width left column has
     nowhere to go on a phone. Toggle button + scrim only exist in this
     range. */
  @media (max-width:750px){
    .sidebar{position:fixed;top:0;left:-220px;height:100vh;z-index:1000;
      transition:left .2s ease;box-shadow:var(--shadow-panel);}
    .sidebar.open{left:0;}
    .sidebar-toggle{display:flex;align-items:center;justify-content:center;}
    .sidebar-scrim.open{display:block;}
    .main-content{padding-top:56px;}
    /* The fixed burger toggle (12px + 36px wide) overlaps the topbar's
       left edge on phones — clear it so + ADD ENTRY isn't cut off. Both
       create buttons + the user menu don't fit one phone-width row, so
       let the bar wrap instead of clipping. */
    .actionframe-inner{padding-left:60px;flex-wrap:wrap;}
    /* Phone user menu is icon-only: avatar + ▾, name hidden — with the
       shortened button labels everything fits on one line. */
    .user-bar .user-name{display:none;}
    /* Phones: the topbar row is already full — the SYNC indicator is
       informational only, so it's the first thing to go. */
    .refresh-status{display:none;}
    /* 2 columns, not 1 — a single 200px+ column forces 4+ KPI tiles into
       ~320px of pure scrolling before any real page content shows. Two
       narrower tiles per row still has plenty of room for a value + label
       at phone widths (labels already wrap). */
    .home-grid{grid-template-columns:1fr 1fr;}
    /* Fixed (not min) height, tightened padding/line-height — every
       kpi-tile in a row is the SAME small footprint regardless of whether
       its own label happens to wrap to 1 or 2 lines, instead of each tile
       only being as tall as its own content. overflow:hidden is a safety
       net so a rare 3-line label clips instead of visually growing the
       card past its neighbors. */
    .kpi-tile{height:68px;padding:8px 12px;overflow:hidden;}
    .kpi-tile-value{font-size:18px;}
    .kpi-tile-label{font-size:10px;line-height:1.25;}
    /* Vault cards (PRIVATE VAULT / group vault cards) carry an extra
       .home-card-actions icon row (up to 4 buttons at the 40px touch size
       set below) that needs close to the full card width — cramped/wrapping
       badly at a 2-up half-width. Shortcut/ghost/quick-action cards have no
       such row and stay 2-up. */
    .home-card-vault{grid-column:1 / -1;}
    /* Desktop's 110px min-height was sized for the old always-visible
       .home-card-actions button row. That row is .row-quick-action, hidden
       on mobile (below) in favor of the "⋯" menu, so every card here is
       just icon+label+hint — 110px leaves a large empty gap under the text. */
    .home-card{min-height:0;padding:12px 16px;}
    /* The "⋯" menu button in .home-card-head inherits the global 40px
       mobile touch-target bump (--btn-icon-size, set above) — sized right
       for a dense table row, but visually oversized/poorly framed next to
       a home-card's much smaller icon+label header. Sized down specifically
       here and pulled toward the card's own corner (negative margin
       matching .home-card's 16px padding) instead of sitting inset with
       awkward space around it. */
    .home-card-head .row-menu-toggle-quickonly{width:32px;height:32px;margin:-4px -6px -4px 0;}
    /* Interior rows were desktop-only: fixed-width price/columns pushed
       buttons off-screen at phone widths. Let rows wrap and price/actions
       drop to a full-width line under the item body instead of overflowing. */
    .item-row{flex-wrap:wrap;gap:8px;}
    .item-body{flex:1 1 100%;}
    /* Marketplace PENDING DEALS rows opt out of the wrap above — the
       compact "ITEM · STATUS" / "QTY · QUALITY" mobile card (below) is short
       enough that CHAT belongs beside it, not wrapped onto its own line. */
    .market-pending-row{flex-wrap:nowrap;}
    .market-pending-row .item-body{flex:1 1 auto;min-width:0;}
    .market-pending-desktop{display:none;}
    .market-pending-mobile{display:block;}
    /* Chats dashboard rows — same "keep the action buttons beside the
       text" fix as Marketplace's Pending Deals above, applied to the
       thread list. The merged role/counterpart/timestamp line lives outside
       .chat-row-top (full-width, own line below — see .item-row.chat-thread-row
       above), so .chat-row-top's item-body is just the one-line title+chip
       here, keeping icon/badge/buttons aligned with it instead of centered
       against a taller multi-line block. */
    .chat-row-top .item-body{flex:1 1 auto;min-width:0;}
    .chat-row-desktop{display:none;}
    .chat-row-mobile{display:block;}
    /* .vault-header (Marketplace/Storefront/Craftable/Crafting Plan/Vault
       Dashboard page headers: name + action buttons) — same "title next to
       buttons, neither shrinks" overflow as the .toolbar headers fixed
       earlier, just a different container class that fix didn't cover. */
    .vault-header{flex-wrap:wrap;row-gap:8px;}
    .summary-row{flex-wrap:wrap;}
    .summary-row .sum-mat{width:auto;flex:1 1 100%;}
    /* .field-row (Refinery's STATION/TARGET VAULT/EXPECTED DURATION row,
       Want-Ad's QUALITY MAX/PRICE/CURRENCY row, Account's CHANGE LOGIN
       EMAIL input+button row) has no mobile override elsewhere — its
       fixed-flex .field children clip/overlap at phone widths. Stack
       instead of squeezing side by side. */
    .field-row{flex-direction:column;}
    /* .field-row-compact opts out of the full-stack rule above — for field
       groups with short labels/narrow inputs that already fit 2-up at
       phone widths via their own flex-wrap, forcing them to 1-per-row
       would waste space rather than fixing an overlap problem. */
    .field-row.field-row-compact{flex-direction:row;flex-wrap:wrap;}
    /* Filter/sort button toolbars: one swipeable line instead of wrapping
       into 3-4 stacked rows of buttons. Scrollbar hidden — the cut-off
       last chip is the scroll affordance, reinforced by the right-edge
       fade below (a plain hard clip reads as "buttons cut off/missing"
       rather than "swipe for more"). .toolbar-wrap opts out (the search +
       price INPUT row should stack, not scroll sideways). */
    .toolbar:not(.toolbar-wrap){flex-wrap:nowrap;overflow-x:auto;
      -webkit-overflow-scrolling:touch;scrollbar-width:none;
      mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);
      -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);}
    .toolbar:not(.toolbar-wrap)::-webkit-scrollbar{display:none;}
    .toolbar:not(.toolbar-wrap) .btn,
    .toolbar:not(.toolbar-wrap) .tool-label{white-space:nowrap;flex-shrink:0;}
    .toolbar.toolbar-wrap{flex-wrap:wrap;}
    /* Segmented controls (entry-modal type toggle, manage-page tabs, report
       sections): 3-4 buttons at the desktop min-width overflow a phone
       panel, and justify-content:center clips BOTH ends (MEMBERS and
       SETTINGS were unreachable at 375px). Let them wrap into a 2×2 grid
       instead; two-button toggles still land on one row. */
    .type-toggle{flex-wrap:wrap;}
    .type-toggle button{min-width:0;flex:1 1 130px;}
    /* Modal footers: three buttons (e.g. Want-Ad's CANCEL / ADD ANOTHER
       LINE / POST) pushed the primary action up to 70px past the modal
       edge on phones — wrap instead of clipping. */
    .modal-footer{flex-wrap:wrap;}
    .footer-left,.footer-right{flex-wrap:wrap;min-width:0;}
    /* Touch targets — .btn-icon (28px), .btn-sm (~23px tall), and the vault
       color swatches (18px) sit well under the ~44px touch guideline.
       Bumped here rather than at the base rule so desktop keeps its
       denser, mouse-driven density. --btn-icon-size is the one var
       .btn-icon already reads, so overriding it here covers every icon
       button in the app in one line. */
    :root{ --btn-icon-size:40px; }
    .btn-sm{min-height:36px;padding:8px 12px;}
    .vault-swatch{width:26px;height:26px;}
    /* Checklist/checkbox rows (storefront + tax material pickers, the
       vault report's own square check indicator): grow the whole tappable
       row, not just the native checkbox glyph (browsers render that at a
       fixed small size we don't control) or the decorative check span. */
    .picker-check{padding:12px 8px;font-size:13px;}
    .report-check{width:22px;height:22px;}
    /* Inventory/Listings row compaction — the LOCATION/STATUS columns and
       the always-visible quick-action icons made these tables wide enough
       to force horizontal scrolling on a phone. On mobile only:
       col-location/col-status columns hide (their info moves into
       .inv-row-secondary under the item name instead), and row-quick-action
       icons hide (the "⋯" menu — always rendered now, see
       row-menu-toggle-quickonly below — holds labeled duplicates instead).
       Desktop is unaffected by any of this. col-origin/col-audience join
       the same treatment — their info repeats in .inv-row-secondary under
       the item name. */
    .col-location,.col-status,.col-grade,.col-class,.col-minq,.col-price,.col-uex-condition,.col-uex-alert-status,.col-origin,.col-audience{display:none;}
    .inv-row-secondary{display:block;}
    .row-quick-action{display:none;}
    /* Follow-up: the NAME/ITEM column (now carrying the secondary line
       too) was still cramped — HTML's default table layout doesn't
       prioritize the column that actually needs the room. Shrink the
       short, fixed-content columns (QUALITY/AMOUNT, QTY/PRICE/AGE) to
       their minimum instead, so NAME/ITEM gets whatever's left over.
       market-mine-forsale-table's QTY/PRICE/AGE moved from columns 3-5 to
       5-7 once ORIGIN/AUDIENCE (hidden on mobile, above) were inserted. */
    #inv-manifest-table td:nth-child(3),#inv-manifest-table td:nth-child(4),
    #inv-manifest-table th:nth-child(3),#inv-manifest-table th:nth-child(4),
    #market-mine-forsale-table td:nth-child(5),#market-mine-forsale-table td:nth-child(6),#market-mine-forsale-table td:nth-child(7),
    #market-mine-forsale-table th:nth-child(5),#market-mine-forsale-table th:nth-child(6),#market-mine-forsale-table th:nth-child(7)
      {width:1%;white-space:nowrap;}
    /* Storefront sidebar's item table (ITEM/MIN Q/PRICE RANGE/STATUS/action)
       had the same squeeze — with no width hint the browser shrank MIN Q/
       PRICE RANGE unevenly and let STATUS's "OVERSTOCK OK" chip word-wrap
       mid-label instead of just scrolling. MIN Q/PRICE RANGE now hide via
       col-minq/col-price above (moved into the item name's secondary line
       instead); only the action column still needs shrinking here so
       STATUS keeps the room it needs for two stacked chips. */
    .storefront-items-tbl td:nth-child(5),.storefront-items-tbl th:nth-child(5)
      {width:1%;white-space:nowrap;}
    /* Smaller chip specifically in the secondary line — the standard
       .listed-chip padding/font-size was sized for a spacious desktop
       column, not a line already sharing space with the item name. */
    .inv-row-secondary .listed-chip,.inv-row-secondary .crafted-chip{font-size:9px;padding:1px 6px;}
  }

  /* KPI tiles in portrait on a wider-than-phone screen (tablet portrait,
     phablet, a rotated monitor) — at this width the max-width:750px block
     above still forces the 2-per-row phone layout, leaving obvious empty
     width unused. min-width:480 excludes real phones (where the 2-up rule
     above should still apply) — :has(.kpi-tile) scopes this to actual KPI
     grids only, since .home-grid is shared with the Home page's vault/
     shortcut cards, which need their own wider layout. */
  @media (orientation:portrait) and (min-width:481px){
    .home-grid:has(.kpi-tile){grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:8px;}
    .home-grid:has(.kpi-tile) .kpi-tile{height:60px;padding:6px 10px;}
    .home-grid:has(.kpi-tile) .kpi-tile-value{font-size:16px;}
    .home-grid:has(.kpi-tile) .kpi-tile-label{font-size:9px;line-height:1.2;}
  }

  /* row-menu-item-quickonly/row-menu-toggle-quickonly — the mobile-only
     "⋯" duplicates of Inventory/Listings' quick actions (see the
     max-width:750px block above) would be redundant next to the
     still-visible desktop icons, so they're hidden here instead. */
  @media (min-width:751px){
    .row-menu-item-quickonly,.row-menu-toggle-quickonly{display:none;}
  }

  /* Material bulk-entry batch list (right column, mirrors the Component
     side-col) — one row per staged-but-not-yet-saved material, Quality/
     Amount/Location/Vault all editable inline, ✕ to drop a row before the
     final Save. One line per row, with a header line above labeling each
     column — same flex widths shared between .batch-header and .batch-row
     so values line up under their label. */
  .batch-header{display:flex;align-items:center;gap:6px;padding:2px 4px 4px;border-bottom:1px solid rgba(var(--overlay-rgb),0.08);font-size:10px;letter-spacing:0.5px;color:var(--text-dim);}
  .batch-row{display:flex;align-items:center;gap:6px;padding:4px;border-bottom:1px solid rgba(var(--overlay-rgb),0.05);font-size:11px;}
  .batch-row:last-child{border-bottom:none;}
  /* Wide enough for the longest real material names (e.g. Hephaestanite,
     Feynmaline) to actually read, not truncate — the modal is fixed at
     940px now, there's room. */
  .batch-header .bh-name,.batch-row .batch-name{width:110px;flex-shrink:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text);}
  .batch-header .bh-quality,.batch-row input.batch-quality{width:44px;flex-shrink:0;text-align:center;}
  .batch-header .bh-amount,.batch-row input.batch-amount{width:52px;flex-shrink:0;text-align:center;}
  .batch-header .bh-location,.batch-row select.batch-location{width:108px;flex-shrink:0;}
  .batch-header .bh-vault,.batch-row select.batch-vault{width:64px;max-width:64px;flex-shrink:0;}
  /* margin-left:auto pins the remove button to the row's true right edge
     even when the fixed-width columns above don't add up to the row's
     full width — without it, everything (including ✕) packs to the left
     with dead space on the right instead. */
  .batch-header .bh-remove{width:16px;flex-shrink:0;margin-left:auto;}
  .batch-row .btn-icon.danger{width:16px;height:16px;padding:0;font-size:11px;line-height:1;flex-shrink:0;margin-left:auto;}
  /* Same fixed height + font for every field in the row, INCLUDING the
     Vault <select> — native <select> chrome otherwise renders visibly
     taller/smaller than a same-padding <input> even with identical
     font-size, which is why Vault previously looked out of place. */
  .batch-row input,.batch-row select{height:22px;padding:2px 4px;font-size:11px;line-height:1;}

  /* Footer mirrors the fieldset's two content columns: DELETE aligns under
     where the left column starts, CANCEL/SAVE align under where the right
     column ends — so the action row visually belongs to the form above it
     instead of floating as an unrelated bar. Single-column (Material) forms
     still get the same left/right split, just with nothing to the left. */
  .modal-footer{display:flex;gap:16px;padding:14px 18px;border-top:1px solid var(--border-strong);position:sticky;bottom:0;background:var(--bg-panel);}
  .footer-left{flex:1;display:flex;}
  .footer-right{flex:1.2;display:flex;justify-content:flex-end;gap:8px;}
  /* flex:1 was tuned for the ~380-440px of footer space a 420-480px modal
     leaves after CANCEL — filling one column of a fixed-width footer. The
     "roomy" modal tier (styles.css, modal-box--w-roomy) has ~2x that footer
     width, and flex:1 alone stretched the button to match — a 4-letter
     SAVE spanning 600+px. max-width keeps it a comfortably sized button on
     any modal width; doesn't affect the 420-480px tier, which was already
     narrower than this cap. */
  .footer-right .btn-primary{flex:1;max-width:240px;}

  .form-error{color:var(--danger-text-hover);font-size:11px;background:rgba(var(--danger-rgb),0.08);border:1px solid rgba(var(--danger-rgb),0.3);padding:8px 10px;border-radius:4px;display:none;}
  .form-error.show{display:block;}

  /* Toast system — replaces native alert()/confirm() for one-shot
     notices (confirmDialog still covers real yes/no confirmations).
     Stacked bottom-right, auto-dismiss handled in JS (toast() in app.js). */
  .toast-stack{position:fixed;bottom:20px;right:20px;z-index:1200;
    display:flex;flex-direction:column;gap:8px;max-width:320px;}
  .toast{display:flex;align-items:center;gap:10px;padding:10px 14px;
    background:var(--bg-dropdown);border:1px solid rgba(var(--overlay-rgb),0.12);border-radius:4px;
    box-shadow:var(--shadow-dropdown);font-size:11px;color:var(--text);
    animation:toast-in .15s ease-out;}
  .toast.success{border-color:rgba(var(--success-rgb),0.35);}
  .toast.success i{color:var(--success);}
  .toast.error{border-color:rgba(var(--danger-rgb),0.4);}
  .toast.error i{color:var(--danger-text-hover);}
  @keyframes toast-in{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:translateY(0);}}

  /* Loading skeleton — shown only where there's a real async gap between
     a view's shell rendering and its data arriving (vault switch,
     marketplace load); the very first login load is already covered by
     the login-gate spinner, and most other views render from data
     that's already in memory by the time they're built. */
  .skeleton-row{display:flex;align-items:center;gap:12px;padding:9px 14px;}
  .skeleton-bar{height:12px;border-radius:3px;flex-shrink:0;
    background:linear-gradient(90deg,rgba(var(--overlay-rgb),0.04) 25%,rgba(var(--overlay-rgb),0.11) 37%,rgba(var(--overlay-rgb),0.04) 63%);
    background-size:400% 100%;animation:skeleton-shimmer 1.4s ease infinite;}
  @keyframes skeleton-shimmer{0%{background-position:100% 50%;}100%{background-position:0 50%;}}

  /* Guided tour (onboarding + reactivation replay) — spotlight ring +
     tooltip card, driven entirely by tour.js. Sits above .toast-stack
     (1200, highest z-index elsewhere in the app) so a tour step is never
     hidden behind a toast.
     Positioning math lives in tour.js (see TOUR_ZOOM there) — it divides
     the getBoundingClientRect() values by html's zoom:1.2 before writing
     them to top/left/width/height, because these are position:fixed
     descendants of the zoomed html and the browser re-applies that 1.2 to
     their own inline px styles, which would otherwise double-scale them. */
  #tour-spotlight{position:fixed;z-index:1300;border-radius:6px;
    box-shadow:0 0 0 9999px rgba(0,0,0,0.65),0 0 0 2px var(--accent);
    pointer-events:none;transition:top .2s ease,left .2s ease,width .2s ease,height .2s ease;}
  #tour-tooltip{position:fixed;z-index:1301;width:280px;max-width:90vw;
    background:var(--bg-panel);border:1px solid rgba(var(--accent-rgb),0.4);border-radius:8px;
    box-shadow:var(--shadow-modal);padding:14px 16px;
    transition:top .2s ease,left .2s ease,width .2s ease;}
  /* Deep-dive steps (RECORD/CRAFT/HYBRID, storefront buy-orders, Group vs
     Org) run longer than a one-liner and carry an illustrative mockup —
     needs more room than the default 280px or both wrap awkwardly. */
  #tour-tooltip.has-demo{width:340px;}
  /* Launcher ring — marks the button that OPENS the modal a tour step is
     showing (tour.js step.launcherSelector). A pulsing outline on top of
     the dark overlay, not a second cutout: the spotlight's box-shadow
     trick can only punch one hole, and the dimmed-but-ringed look still
     reads clearly as "this is where you'll find it later". */
  #tour-launcher-ring{position:fixed;z-index:1300;border:2px solid var(--accent);
    border-radius:6px;pointer-events:none;
    transition:top .2s ease,left .2s ease,width .2s ease,height .2s ease;
    animation:tour-ring-pulse 1.5s ease-in-out infinite;}
  @keyframes tour-ring-pulse{
    0%,100%{box-shadow:0 0 4px rgba(var(--accent-rgb),0.4);}
    50%{box-shadow:0 0 16px rgba(var(--accent-rgb),0.9);}
  }

  .tour-tooltip-step{font-size:10px;letter-spacing:1px;color:var(--text-dim);margin-bottom:6px;}
  .tour-tooltip-title{font-family:'Orbitron',monospace;font-size:12px;letter-spacing:1px;
    color:var(--accent);margin-bottom:8px;}
  .tour-tooltip-body{font-size:12px;line-height:1.5;color:var(--text);margin-bottom:14px;}
  .tour-tooltip-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;}
  .tour-tooltip-nav{display:flex;gap:8px;}

  /* Illustrative mockups (tour.js step.demo) — a self-contained, purely
     visual facsimile of a feature a brand-new account has no real data for
     yet (no Group Vault, no storefront). Deliberately NOT built from real
     .sidebar-link/.item-row markup (those assume a parent layout/width this
     tooltip doesn't have) and deliberately non-interactive — nothing here
     reads or writes real app state, so it can never drift out of sync with
     it or fire a real action by accident. */
  .tour-demo{margin-bottom:14px;padding:10px;background:rgba(var(--accent-rgb),0.06);
    border:1px solid rgba(var(--accent-rgb),0.2);border-radius:6px;pointer-events:none;}
  .tour-demo-row{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--text);
    padding:5px 0;}
  .tour-demo-row i{color:var(--accent);width:14px;text-align:center;flex-shrink:0;}
  .tour-demo-row .tour-demo-tag{margin-left:auto;font-size:9px;letter-spacing:0.5px;
    color:var(--text-dim);white-space:nowrap;}
  .tour-demo-toggle{display:flex;gap:6px;margin-bottom:6px;}
  .tour-demo-chip{font-size:9px;letter-spacing:1px;padding:3px 8px;border-radius:3px;
    background:rgba(var(--overlay-rgb),0.06);color:var(--text-dim);}
  .tour-demo-chip.active{background:rgba(var(--accent-rgb),0.18);color:var(--accent);}
  .tour-demo-bar{height:5px;border-radius:3px;background:rgba(var(--overlay-rgb),0.08);
    overflow:hidden;margin:4px 0;}
  .tour-demo-bar-fill{height:100%;background:var(--accent);}
  .tour-demo-btn{font-size:9px;letter-spacing:1px;padding:4px 10px;border-radius:4px;
    background:rgba(var(--accent-rgb),0.15);color:var(--accent);border:1px solid rgba(var(--accent-rgb),0.3);}

  /* ── HELP page (#/help, js/help.js) ─────────────────────────────────── */
  .help-section{border:1px solid rgba(var(--overlay-rgb),0.08);border-radius:6px;
    margin-bottom:8px;background:rgba(var(--overlay-rgb),0.02);}
  .help-section summary{cursor:pointer;padding:10px 12px;font-size:12px;
    letter-spacing:1px;color:var(--accent);display:flex;align-items:center;gap:10px;
    user-select:none;list-style:none;}
  .help-section summary::-webkit-details-marker{display:none;}
  .help-section summary::after{content:"›";margin-left:auto;opacity:0.5;
    transition:transform 0.15s;}
  .help-section[open] summary::after{transform:rotate(90deg);}
  .help-section[open]{background:rgba(var(--overlay-rgb),0.06);}
  .help-body{padding:2px 14px 12px;font-size:12.5px;line-height:1.55;
    color:var(--text);}
  .help-body ul{margin:6px 0 8px;padding-left:18px;}
  .help-body li{margin:4px 0;}
  .help-body b{color:var(--text);}
  .help-mono{font-family:monospace;font-size:11px;opacity:0.8;}
  .help-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px;}
  .help-table th{text-align:left;padding:6px 8px;color:var(--accent);
    letter-spacing:1px;font-size:10px;border-bottom:1px solid rgba(var(--overlay-rgb),0.12);}
  .help-table td{padding:5px 8px;border-bottom:1px solid rgba(var(--overlay-rgb),0.06);
    vertical-align:top;}
  .help-group-label{margin:14px 0 6px;}
  .help-group-label:first-child{margin-top:0;}
  .help-table .help-subhead{color:var(--accent);font-size:10px;letter-spacing:1px;
    padding-top:10px;border-bottom:none;}

  /* ── Hover help mode (js/help.js) ───────────────────────────────────── */
  #help-hover-tooltip{position:fixed;z-index:2000;max-width:320px;display:none;
    background:rgba(10,20,28,0.97);border:1px solid rgba(var(--accent-rgb),0.4);
    border-radius:6px;padding:8px 10px;font-size:11.5px;line-height:1.45;
    color:rgba(255,255,255,0.92);pointer-events:none;
    box-shadow:0 4px 16px rgba(0,0,0,0.5);}
  #help-hover-tooltip.open{display:block;}
  /* Scoped to help.js's help-hint-target class, not a blanket tag selector —
     otherwise controls with no registered hint would show the "?" cursor
     with no tooltip to back it up. */
  body.help-mode .help-hint-target{cursor:help;}
  .user-menu-btn.help-mode-active{color:var(--accent);
    border-color:rgba(var(--accent-rgb),0.5);}

/* Sidebar mode switch — slim, separated from the nav groups above it. */
.sidebar-mode-switch{margin-top:14px;padding-top:10px;
  border-top:1px solid rgba(var(--overlay-rgb),0.08);}
.sidebar-mode-switch .sidebar-link{color:var(--text-dim);font-size:11px;}
.sidebar-mode-switch .sidebar-link:hover{color:var(--accent);}

/* One-time simple-mode rollout banner (index.html #simple-mode-banner). */
#simple-mode-banner{position:fixed;bottom:18px;left:50%;
  transform:translateX(-50%);z-index:300;display:flex;gap:10px;
  align-items:center;max-width:min(92vw,560px);padding:10px 14px;
  background:var(--bg-dropdown);border:1px solid rgba(var(--accent-rgb),0.35);
  border-radius:8px;font-size:12px;color:var(--text);
  box-shadow:var(--shadow-dropdown);}
#simple-mode-banner.hidden{display:none;}

/* MORE fold for optional form fields — native <details>, closed by
   default: required fields stay the visible form. */
details.form-more{margin:0 0 10px;}
/* Bumped from 11px/--text-dim — the fold toggle was easy to miss entirely
   against the surrounding dim, small-caps form labels. */
details.form-more summary{cursor:pointer;list-style:none;font-size:12px;
  letter-spacing:1px;color:var(--text);user-select:none;}
details.form-more summary::-webkit-details-marker{display:none;}
details.form-more summary::before{content:"▸ ";}
details.form-more[open] summary::before{content:"▾ ";}
details.form-more[open] summary{margin-bottom:8px;}

/* Task-first HOME quick-action cards (simple mode). */
.home-card-primary{border-color:rgba(var(--accent-rgb),0.45);}
.home-card-primary .home-card-head i{color:var(--accent);}

/* Keyboard-operable clickable divs — same ring the .btn focus-visible rule
   uses, for every role="button" render site. */
[role="button"]:focus-visible{outline:2px solid var(--accent);
  outline-offset:2px;}


/* ── Reduced motion ──────────────────────────────────────────────────────
   Kills the infinite animations (spinner keeps a static arc, skeletons a
   static bar, tour ring a static outline) and collapses transitions for
   users who asked their OS for less motion. */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:0.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:0.01ms !important;
  }
}


/* ── Wave-4 polish ──────────────────────────────────────────────────────
   Utility classes for the account modal's five hand-repeated section
   dividers / prose blocks / section headings, plus the skip-to-content
   link. */
.modal-section{margin-top:18px;padding-top:14px;
  border-top:1px solid rgba(var(--overlay-rgb),0.08);}
.modal-prose{color:var(--text);line-height:1.5;font-size:12px;}
.modal-section-heading{margin-bottom:10px;text-transform:uppercase;
  letter-spacing:1px;}
.skip-link{position:absolute;left:8px;top:-40px;z-index:1300;
  background:var(--accent);color:var(--bg);padding:8px 12px;border-radius:6px;
  font-size:12px;font-weight:600;transition:top 0.1s;}
.skip-link:focus{top:8px;}
