@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500;600&display=swap');

/* Fluid root font-size. Almost every size in this app is rem-based (Tailwind's text-*, p-*, gap-*,
   space-y-*, w-*, m-* utilities all are), so this one value is the master density control for the
   whole UI - shrink it and every panel, gap and label shrinks proportionally.

   It used to be a flat 118%, which was tuned for a big 4K canvas and left 1080p / laptop screens
   badly cramped (e.g. the rem-based BOM sidebar rendered ~453px wide instead of 384px). Now it
   scales with the window width instead:
       ~1920px viewport  -> ~100%   (1080p: no inflation, everything breathes)
       ~3072px viewport  -> ~118%   (4K at 125% OS scaling: unchanged from before)
       wider             -> up to ~131%, then capped
   The `rem + vw` form (not pure vw) keeps browser zoom working. Literal-pixel classes like
   text-[10px] are unaffected by design - they stay fixed. Clamped 15px..21px at the extremes. */
html {
  font-size: clamp(0.9375rem, 0.7rem + 0.25vw, 1.3125rem);
}

/* ============================================================
   Design tokens - "dark luxury glass" language: layered translucent panels over a softly-lit
   graphite backdrop, one warm copper accent, cool blue/violet reserved for secondary meaning
   (buy vs build, per-tool identity), green/red for profit/loss. Depth comes from blur + a
   brighter top edge catching light + a soft shadow, not from clip-path or neon glow brackets.
   ============================================================ */
:root {
  --bg-0: #0a0a0b;
  --text: #e9e9ea;
  --text-soft: #a3a3a7;
  --text-mute: #6b6b70;

  --accent: #d3915a;
  --accent-2: #a86336;
  --accent-rgb: 211,145,90;

  --blue: #6a98de;
  --blue-rgb: 106,152,222;
  --buy: #6a98de;
  --buy-rgb: 106,152,222;

  --violet: #9a86d6;
  --violet-rgb: 154,134,214;

  --green: #4cc491;
  --green-rgb: 76,196,145;

  --red: #dd6b64;
  --red-rgb: 221,107,100;

  /* Cost - a distinct warm gold, separate from --accent/--green, so "this is money going out" never
     reads the same as "this is profit/positive" at a glance. */
  --cost: #d9b84a;
  --cost-rgb: 217,184,74;

  /* Copper ramp (replaces Tailwind's orange/amber) */
  --copper-100: #f3ddc7;
  --copper-200: #ecc9a3;
  --copper-300: #e3b17d;
  --copper-400: #d3915a;
  --copper-500: #d3915a;
  --copper-600: #b97847;
  --copper-700: #a86336;
  --copper-800: #855231;
  --copper-900: #5f3a22;
  --copper-950: #3d2515;

  /* Blue ramp (replaces Tailwind's cyan) */
  --blue-300: #b7d3f5;
  --blue-400: #8bb3ea;
  --blue-500: #6a98de;
  --blue-600: #5483c9;
  --blue-700: #3f6cb0;
  --blue-800: #345a8f;
  --blue-900: #2a4670;

  /* Violet ramp (replaces Tailwind's purple) */
  --violet-100: #ded7f7;
  --violet-300: #c3b4ef;
  --violet-400: #a98fe4;
  --violet-500: #9a86d6;
  --violet-600: #8a72c9;
  --violet-700: #7a5fb8;
  --violet-800: #6a4fa3;

  /* Green ramp (profit) */
  --green-300: #a3e8cb;
  --green-400: #4cc491;
  --green-500: #3fae7c;
  --green-600: #349a6c;
  --green-700: #2b7f59;
  --green-800: #235f45;

  /* Red ramp (loss) */
  --red-300: #f0b3ae;
  --red-400: #dd6b64;
  --red-500: #cf5a53;

  /* Glass surface - matches the approved mockup exactly: a near-transparent WHITE sheen (not an
     opaque brown "surface color") over backdrop blur, so the page's own colorful background
     shows through tinted. This is the actual glass material - do not reintroduce an opaque
     backing color here, that defeats the whole effect. */
  --glass-fill: linear-gradient(160deg, rgba(255,255,255,0.05), rgba(255,255,255,0.013) 60%);
  --glass-border: rgba(255,255,255,0.09);
  --glass-border-top: rgba(255,255,255,0.18);
  /* Kept tight on purpose: panels sit only 12-14px apart, and the old 40px blur radius bled
     well past that gap, smearing a visible shadow seam across the top of the next panel down
     instead of a clean gap between two independent rounded rectangles. */
  --glass-shadow: 0 3px 12px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.05);
  --blur: 22px;

  --radius-sm: 7px;
  --radius-btn: 9px;
  --radius-panel: 14px;
  --radius-card: 16px;
  --radius-pill: 999px;
  --radius-input: 7px;
}

/* Page composition: a padded canvas with floating rounded glass panels and visible gaps between
   them (header, stat strip, icon rail, viewport, BOM sidebar), not edge-to-edge toolbar chrome -
   that gap + rounded-corner language is what actually reads as "glass" rather than a flat reskin. */
body {
  background:
    radial-gradient(ellipse 1100px 700px at 10% -10%, rgba(var(--accent-rgb),0.07) 0%, transparent 60%),
    radial-gradient(ellipse 900px 750px at 95% 12%, rgba(var(--violet-rgb),0.05) 0%, transparent 60%),
    var(--bg-0);
  background-attachment: fixed;
  color: var(--text);
  font-family: 'Inter', sans-serif;
  height: 100vh;
  box-sizing: border-box;
  /* Outer canvas inset + gaps between the floating panels - fluid for the same reason as the root
     font above. Near-unchanged (~27px / 12px) on a wide 4K canvas, tightened to ~17px / 8px at
     1080p so the middle viewport keeps its width. */
  padding: clamp(16px, 0.9vw, 28px);
  gap: clamp(8px, 0.4vw, 12px);
  user-select: none;
  -webkit-user-select: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Ensure inputs, textareas, and designated text sections are freely highlightable and copyable */
input, textarea, select, [contenteditable="true"], .mono, aside, .selectable {
  user-select: text !important;
  -webkit-user-select: text !important;
}

/* .mono is applied throughout the app to every ISK/quantity/percentage value - repointed to a
   monospace built for data (tabular figures) instead of the old sci-fi display font. .rajdhani
   is the heading/wordmark hook - repointed to Inter with tight tracking instead of a wide
   uppercase tech-font look. */
.mono { font-family: 'IBM Plex Mono', monospace; font-variant-numeric: tabular-nums; }
.rajdhani { font-family: 'Inter', sans-serif; font-weight: 600; letter-spacing: -0.005em; }

.section-label {
  font-family: 'Inter', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-mute);
}

#viewport {
  cursor: grab;
  overflow: hidden;
  position: relative;
}

#pan-zoom-content {
  transform-origin: 0 0;
  position: absolute;
  top: 0;
  left: 0;
  min-width: 100%;
  min-height: 100%;
}

#tree-svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1;
}

img {
  image-rendering: -webkit-optimize-contrast;
  image-rendering: crisp-edges;
}

.diagram-node {
  z-index: 2;
  position: relative;
  cursor: pointer;
  transform: translateZ(0);
  backface-visibility: hidden;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease, transform 0.2s ease;
}

.diagram-node:hover {
  transform: translateY(-2px);
}

/* Prefixed with .diagram-node (always present alongside these on tree cards) so specificity
   (0,0,2,0) reliably beats the .shadow-lg/.shadow-2xl compat-layer mapping the same cards also
   carry - both were single-class selectors of equal specificity before, so source order alone
   decided the tie and the compat layer (declared later in the file) was silently winning,
   flattening this glow back to a plain shadow no matter which node was selected. */
.diagram-node.node-selected {
  border-color: var(--green) !important;
  box-shadow: var(--glass-shadow), 0 0 0 1px rgba(var(--green-rgb),0.4), 0 0 22px rgba(var(--green-rgb),0.35) !important;
  opacity: 1 !important;
}
.diagram-node.node-child-highlight {
  border-color: var(--green) !important;
  box-shadow: var(--glass-shadow), 0 0 14px rgba(var(--green-rgb),0.25) !important;
  opacity: 1 !important;
}
.diagram-node.node-parent-highlight {
  border-color: var(--accent) !important;
  box-shadow: var(--glass-shadow), 0 0 16px rgba(var(--accent-rgb),0.35) !important;
  opacity: 1 !important;
}
.diagram-node.node-dimmed {
  opacity: 0.25 !important;
}

.ring-4 { animation: pulse-ring 0.8s ease-out; }
@keyframes pulse-ring {
  0% { box-shadow: 0 0 0 0px rgba(var(--accent-rgb), 0.8); }
  100% { box-shadow: 0 0 0 15px rgba(var(--accent-rgb), 0); }
}

* {
  scrollbar-width: thin;
  scrollbar-color: var(--glass-border) transparent;
}
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--glass-border-top); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }

/* ============================================================
   Glass surface - ONE material, applied everywhere (header, icon rail, flyout panels, BOM
   sidebar, drawers, modals, AND tree node cards), exactly matching the approved mockup's single
   .glass class: translucent white sheen + real backdrop blur + edge-lit border + soft shadow.
   Every one of these gets backdrop-filter - do not split cards out onto a blur-less variant,
   that was the earlier mistake that produced the flat "opaque brown card" look.
   ============================================================ */
.glass-card, .glass-panel, header, .icon-rail, .flyout-panel, #status-badge,
#blueprint-browser-drawer, #paste-modal > div, .stat-strip, #bom-sidebar, #history-drawer,
#invention-sidebar, #viewport, #journal-main-area, #invention-main-area, #viewport-hint {
  background: var(--glass-fill);
  backdrop-filter: blur(var(--blur));
  -webkit-backdrop-filter: blur(var(--blur));
  border: 1px solid var(--glass-border);
  border-top-color: var(--glass-border-top);
  border-radius: var(--radius-panel);
  box-shadow: var(--glass-shadow);
}
.glass-card { border-radius: var(--radius-card); }
header { height: 56px; flex-shrink: 0; position: relative; }
/* Site title centered on the header's own total width via absolute positioning - NOT flex/grid
   distribution. Two earlier attempts both touched the header's actual layout and both broke
   something: a flex `flex:1` child between the two side groups centered in whatever space was left
   OVER between them, which shifted per page since the right-hand group's width varies a lot
   (Calculator's utility cluster vs. the other pages' bare theme+login) - reported as misaligned.
   Converting the header to a 3-column grid fixed that centering but changed how the two side
   groups themselves size and wrap internally (their flex-wrap content wrapped a second row that
   never happened under plain flex) - reported directly as "buttons moved, login isn't where it
   used to be". Absolute positioning leaves the header's own flex layout for the left/right groups
   completely untouched - pixel-identical to how it always worked - while the title still lands at
   the exact page-center X on every page, since the math comes from the header's own box width, not
   from either sibling's content. */
.site-title-centered {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  white-space: nowrap;
}

/* Data-load status pill - just the dot at rest, the full text (INITIALIZING.../RECIPES & PRICES
   LOADED/an error) slides out on hover instead of permanently occupying header width for something
   only worth a glance right after page load. No JS changes needed - every call site still just sets
   #status-text's textContent and #status-dot's className exactly as before; this is a pure CSS
   reveal, keyed off :hover on the badge itself. */
.status-badge-compact { padding: 6px; transition: padding 0.15s ease; }
.status-badge-compact:hover { padding: 6px 10px; }
.status-badge-text {
  display: inline-block;
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  opacity: 0;
  transition: max-width 0.2s ease, opacity 0.15s ease, margin-left 0.2s ease;
}
.status-badge-compact:hover .status-badge-text { max-width: 220px; opacity: 1; margin-left: 8px; }

/* Same soft-tint treatment as .btn-glass-muted - a fully transparent resting state (the original
   design here) is too easy to miss against the dark background, same problem those toolbar buttons
   had. A faint permanent lime wash makes them read as clickable controls at a glance. */
.icon-btn {
  width: 30px; height: 30px; border-radius: var(--radius-btn);
  display: flex; align-items: center; justify-content: center;
  color: var(--accent); border: 1px solid rgba(var(--accent-rgb),0.35);
  background: rgba(var(--accent-rgb),0.16);
  cursor: pointer;
  transition: all 0.15s ease;
}
.icon-btn svg { width: 15px; height: 15px; stroke: currentColor; fill: none; }
.icon-btn:hover { color: var(--accent); background: rgba(var(--accent-rgb),0.28); border-color: rgba(var(--accent-rgb),0.55); }

/* ===== Header brand + global search (index.html) ===== */
/* Fixed width regardless of title text length ("DEEP-INDUSTRY" vs "EVE INDUSTRY LEDGER" vs
   "INVENTION CALCULATOR") so nav-tabs, glued directly after this block, lands at the identical X
   position on all three pages. */
.header-brand { width: 232px; }
/* flex:0 0 auto (not the old 1 1 auto) - this box now lives inside the Calculator's stat-strip
   search card, a COLUMN flex container (.stat), not the header's row layout it was original built
   for. flex-grow along a column's main axis means "grow to fill height", which silently stretched
   this box to the full card height while the <input> inside it stayed its own natural (much
   shorter) height - the search icon centers correctly on ITS OWN parent (this box), so it ended up
   centered on the tall invisible box instead of on the actual visible input/placeholder text,
   reported directly as "the lens icon isn't centered with the search text". Sizing to content
   height fixes that; width still comes from .header-search-input's own width:100% below. */
.header-search-wrap { position: relative; flex: 0 0 auto; min-width: 140px; max-width: 460px; }
/* This is the primary action of the whole page (search for the item to build), so unlike
   .field-line's "nothing until you interact" rule, it carries a persistent lime tint at rest -
   the same soft-tint language as .icon-btn/.btn-glass-muted - so it visibly reads as THE thing to
   click, not just another quiet field in the header row. */
.header-search-input {
  width: 100%;
  background: rgba(var(--accent-rgb),0.07);
  border: 1.5px solid rgba(var(--accent-rgb),0.3);
  border-radius: var(--radius-btn);
  padding: 8px 12px 8px 34px;
  color: var(--text);
  font-size: 12.5px;
  font-family: 'IBM Plex Mono', monospace;
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.header-search-input::placeholder { color: var(--text-soft); }
.header-search-input:hover { border-color: rgba(var(--accent-rgb),0.45); background: rgba(var(--accent-rgb),0.1); }
.header-search-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(var(--accent-rgb),0.18); background: rgba(var(--accent-rgb),0.11); }
.header-search-icon { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--accent); pointer-events: none; }

/* ===== Pilot identity badge (replaces the SSO login button once authenticated - shared by all
   four pages via esi.js's updateEsiUserUI()) ===== */
/* position:relative on the CONTAINER, not .pilot-badge - the switcher popover below is a sibling of
   .pilot-badge (both direct children of #esi-login-container), not a child of it, so the container
   itself is what the popover's absolute positioning anchors against. */
#esi-login-container { position: relative; }
.pilot-badge {
  display: flex; align-items: center; gap: 9px;
  padding: 5px 10px 5px 5px;
  border-radius: 999px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--glass-border);
}
.pilot-portrait { width: 28px; height: 28px; border-radius: 50%; border: 1px solid rgba(var(--accent-rgb),0.4); flex-shrink: 0; display: block; }
.pilot-meta { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.pilot-name { font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 140px; }
.pilot-corp { font-size: 10.5px; color: var(--accent); font-family: 'IBM Plex Mono', monospace; white-space: nowrap; }
.pilot-switch-btn {
  color: var(--text-mute); display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; background: none; border: none; cursor: pointer;
  padding: 2px; transition: color 0.15s ease;
}
.pilot-switch-btn:hover { color: var(--accent); }
.pilot-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 6px rgba(var(--accent-rgb),0.7); flex-shrink: 0; }
.pilot-logout {
  color: var(--text-mute); display: flex; align-items: center; justify-content: center;
  margin-left: 2px; flex-shrink: 0; background: none; border: none; cursor: pointer;
  padding: 2px; transition: color 0.15s ease;
}
.pilot-logout:hover { color: var(--red); }

/* ===== Character switcher popover - same anchored-dropdown shape as the LP Store's corp picker
   (.lpstore-corp-popover), sized for a much narrower trigger (the pilot badge, top-right of the
   page) instead of a wide sidebar bar, so it hangs from the right edge rather than stretching full
   width. ===== */
.pilot-switcher-popover {
  position: absolute; top: 100%; right: 0; margin-top: 8px;
  width: 240px; max-width: calc(100vw - 24px);
  z-index: 40; padding: 8px;
}
.pilot-switcher-list { display: flex; flex-direction: column; gap: 2px; max-height: 260px; overflow-y: auto; }
.pilot-switcher-row {
  display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: var(--radius-input);
  cursor: pointer; transition: background 0.15s ease;
}
.pilot-switcher-row:hover { background: rgba(255,255,255,0.06); }
.pilot-switcher-row-active { background: rgba(var(--accent-rgb),0.10); cursor: default; }
.pilot-switcher-portrait { width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0; display: block; }
.pilot-switcher-meta { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; flex: 1; }
.pilot-switcher-name { font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pilot-switcher-ticker { font-size: 10px; color: var(--text-mute); font-family: 'IBM Plex Mono', monospace; }
.pilot-switcher-active-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 5px rgba(var(--accent-rgb),0.7); flex-shrink: 0; }
.pilot-switcher-remove {
  color: var(--text-mute); background: none; border: none; cursor: pointer; flex-shrink: 0;
  font-size: 15px; line-height: 1; padding: 2px 4px; transition: color 0.15s ease;
}
.pilot-switcher-remove:hover { color: var(--red); }
.pilot-switcher-empty { padding: 10px 8px; font-size: 11.5px; color: var(--text-mute); font-style: italic; text-align: center; }
.pilot-switcher-add {
  display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%;
  margin-top: 6px; padding: 7px 8px; border-radius: var(--radius-input);
  background: rgba(255,255,255,0.04); border: 1px dashed var(--glass-border);
  color: var(--text-mute); font-size: 11.5px; font-weight: 600; cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.pilot-switcher-add:hover { background: rgba(var(--accent-rgb),0.10); color: var(--accent); }
/* The blueprint browser is a full-height edge-attached drawer (native-panel pattern), so it
   stays flush and square against the right edge rather than floating with rounded corners. */
#blueprint-browser-drawer { border-radius: 0; border-right: none; border-top: none; border-bottom: none; }
/* Everything else floats with a margin from the viewport edge (see body padding), so it keeps
   full rounded corners on every side. The history drawer is fixed-positioned, so its left/right
   inset needs to match the page padding explicitly rather than relying on flow layout. The bottom
   inset used to be that same 28px, measured from the true viewport edge, back when nothing else
   lived down there - now the site-legal-footer sits below it, so bottom instead keys off
   --footer-clearance (js/ledger.js's updateFooterClearance: the footer's real measured height
   plus the same row-gap that already separates every other flow-sibling pair in the body column,
   not a guess), so the drawer floats flush above the footer using that same gap convention. */
#history-drawer { position: fixed; left: 28px; right: 28px; bottom: var(--footer-clearance, 16px); width: auto; }

/* ===== Buttons ===== */
.btn-glass {
  border-radius: var(--radius-btn);
  border: none;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: 0 6px 18px rgba(var(--accent-rgb),0.3);
  color: #201206;
  font-family: 'Inter', sans-serif;
  font-weight: 600;
  cursor: pointer;
  position: relative;
  transition: filter 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}
.btn-glass:hover {
  filter: brightness(1.08);
  box-shadow: 0 8px 22px rgba(var(--accent-rgb),0.4);
}
.btn-glass:active { transform: translateY(1px); }
/* Soft-tint secondary button - a translucent wash of the accent color instead of either the fully
   transparent/gray treatment (too invisible against a dark background to register as clickable) or
   the solid gradient fill (too loud to use on more than one or two true primary actions per view).
   Same visual weight as .lp-badge-accent ("Built" on job cards) so secondary actions still read as
   on-brand and clickable without competing with the actual primary action for attention. */
.btn-glass-muted {
  background: rgba(var(--accent-rgb),0.14);
  border: 1px solid rgba(var(--accent-rgb),0.25);
  box-shadow: none;
  color: var(--accent);
}
.btn-glass-muted:hover {
  background: rgba(var(--accent-rgb),0.22);
  border-color: rgba(var(--accent-rgb),0.4);
  color: var(--accent);
}

/* ===== Header nav tabs (Calculator / Ledger / Invention cross-links) ===== */
nav.primary-tabs { display: flex; align-items: center; gap: 4px; height: 100%; }
.nav-tab {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 14px;
  border-radius: var(--radius-btn);
  font-size: 12.5px; font-weight: 500;
  color: var(--text-mute);
  cursor: pointer;
  border: 1px solid transparent;
  transition: all 0.15s ease;
}
.nav-tab:hover { color: var(--text-soft); }
.nav-tab svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; flex-shrink: 0; }
/* Coal-neutral: selection/navigation state reads as plain lifted gray, not tinted with the
   accent - accent color is reserved for the brand mark and true primary actions (see .btn-glass),
   not sprinkled onto every "this is selected" state. Same treatment regardless of which page's
   tab is active - the per-page brand color still shows up in the header's logo mark. */
.nav-tab-active, .nav-tab-active-violet {
  color: var(--text) !important;
  background: rgba(255,255,255,0.07);
  border-color: rgba(255,255,255,0.12);
}

/* ===== Stat strip (replaces the old grid-of-boxed-cards summary bar) ===== */
.stat-strip {
  display: flex;
  flex-wrap: wrap;
}
.stat {
  padding: 11px 20px;
  border-right: 1px solid var(--glass-border);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  min-width: 150px;
  flex: 1;
}
.stat:last-child { border-right: none; }
.stat-label { font-size: 10.5px; color: var(--text-mute); letter-spacing: 0.03em; text-transform: uppercase; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stat-value { font-size: 16px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stat-sub { font-size: 10.5px; color: var(--text-mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ============================================================
   Icon rail navigation
   ============================================================ */
.content-row { gap: 12px; }

.icon-rail {
  width: 80px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 18px 0;
  flex-shrink: 0;
  position: relative;
}
.icon-rail-btn {
  width: 58px;
  height: 58px;
  border-radius: 11px;
  background: transparent;
  border: 1px solid transparent;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  transition: background 0.2s ease, border-color 0.2s ease;
  cursor: pointer;
  position: relative;
  flex-shrink: 0;
}
.icon-rail-btn:hover {
  background: rgba(var(--accent-rgb),0.08);
  border-color: rgba(var(--accent-rgb),0.18);
}
/* Unlike nav-tab-active (coal-neutral by design - see that rule's own comment), the rail's active
   tab IS accent-tinted: with only one flyout panel able to be open at a time now, this is the one
   visual link telling you which of the five icons the panel on its right belongs to, not a
   "you are here" navigation cue - a real functional reason to spend the accent color here. */
.icon-rail-btn-active {
  background: rgba(var(--accent-rgb),0.16);
  border-color: rgba(var(--accent-rgb),0.4);
}
/* Base resting state must come BEFORE the -active overrides below - both are two-class
   descendant selectors of equal specificity, so whichever is later in the file wins regardless
   of which one is semantically "more specific". Previously -active was declared first here and
   silently lost to these, leaving the active rail icon stuck at the resting gray color. */
.icon-rail-btn svg {
  width: 19px;
  height: 19px;
  stroke: var(--text-mute);
  transition: stroke 0.2s ease;
}
.icon-rail-btn .icon-label {
  font-family: 'Inter', sans-serif;
  font-size: 8.5px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-mute);
  font-weight: 600;
}
.icon-rail-btn-active svg { stroke: var(--accent); }
.icon-rail-btn-active .icon-label { color: var(--accent); }
.icon-rail-btn:hover svg { stroke: var(--accent); }
.icon-rail-btn:hover .icon-label { color: var(--accent); }

svg[viewBox] { stroke: currentColor; }

/* Single inspector panel (see the HTML comment on #control-sidebar for why this replaced the old
   N-independent-popovers model): one fixed anchor point instead of a runtime-computed stacking
   position, a real header (icon-rail's active tab name + close button) that stays put while the
   body scrolls independently, and exactly one .flyout-section visible inside it at a time. */
.flyout-panel {
  display: none;
  position: absolute;
  left: 88px;
  top: 12px;
  /* Was capped at 640px, which forced the (now taller) System & Structure section to scroll inside
     the panel on every screen. The panel top sits ~211px below the viewport top (header + stat
     strip); no hard cap now, so on a normal-height screen every section fits without an inner
     scrollbar. Very short screens (<800px tall) can still scroll the Structure section - that's the
     one section that can't be shortened further without dropping information. */
  max-height: calc(100vh - 210px);
  width: 344px;
  border-radius: var(--radius-panel);
  z-index: 100;
  padding: 14px 16px;
  flex-direction: column;
}
.flyout-panel.open { display: flex; }

.flyout-panel-header {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  padding-bottom: 10px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--glass-border);
}
.flyout-panel-title {
  flex: 1;
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
  font-family: 'Inter', sans-serif;
}
.flyout-panel-close {
  width: 22px;
  height: 22px;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-mute);
  cursor: pointer;
  flex-shrink: 0;
  transition: all 0.15s ease;
}
.flyout-panel-close svg { width: 13px; height: 13px; stroke: currentColor; }
.flyout-panel-close:hover { color: var(--text); background: rgba(255,255,255,0.08); }

.flyout-panel-body {
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}

.flyout-section { display: none; }
.flyout-section.active { display: block; }

/* One shared vocabulary for every field group across all five sections, replacing what used to be
   a different one-off spacing/label pattern per flyout (uppercase tracked-wide divs in Structure/
   Markets, plain unlabeled rows in Pricing, bold colored spans in Taxes/Contract). */
.fo-group + .fo-group { margin-top: 12px; }
.fo-group-label { display: block; margin-bottom: 6px; }
.fo-inline-label { color: var(--text-soft); flex-shrink: 0; }
.fo-inline-suffix { color: var(--text-mute); flex-shrink: 0; }

/* LP Store corporation picker (js/lpstore.js renderLPStoreCorpList) - search-or-browse over ~180
   corps/19 factions, replacing what used to be a 9-option <select>. Faction headers collapse to one
   line each by default (a flat, always-visible 180-row list would defeat the point of not being a
   giant dropdown); clicking one expands just that faction's own corp rows below it.

   The picker itself lives in #lpstore-corp-popover, absolutely positioned off the persistent
   #lpstore-corp-switcher bar in lpstore.html - it used to be a full flyout-panel section, which
   meant opening/closing a whole overlay every time you wanted to change corp, and that overlay
   covered the ranked-offer list underneath it while open. Anchoring it to the switcher bar instead
   means only a compact area right below the bar is ever covered, and it's reachable without going
   through the icon-rail at all. */
/* Positioning only - background/border/shadow come from the .glass-card class this also carries
   in the HTML, the same surface treatment every other floating panel in the app already uses. */
/* max-width caps it at a sane size regardless of how wide #lpstore-corp-switcher itself is (a
   full-width .lp-card - on anything but a narrow window that's much wider than a compact popover
   should ever be). Without this it stretched to match the switcher bar's own width - confirmed:
   1343px wide on a 1600px browser window - leaving every row's short text floating sparse on the
   left of a mostly-empty box, exactly the "unusable, hard to read" layout reported for Find Item's
   results specifically (its rows have the most text of the three popovers, so the wasted width was
   most obvious there, but all three shared this same unbounded sizing). left/right still handle
   the narrow-screen case, shrinking it below max-width so it never overflows a small viewport. */
.lpstore-corp-popover { position: absolute; top: 100%; left: 12px; right: 12px; max-width: 520px; margin-top: 8px; z-index: 30; padding: 12px; }
.lpstore-corp-list { max-height: 360px; overflow-y: auto; border-radius: var(--radius-input); border: 1px solid var(--glass-border); }
.lpstore-corp-faction-group + .lpstore-corp-faction-group { border-top: 1px solid var(--glass-border); }
.lpstore-corp-faction-header {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 13px 14px; border-left: 4px solid transparent;
  background: rgba(255,255,255,0.02); font-size: 16px; font-weight: 700;
  text-align: left; cursor: pointer;
}
.lpstore-corp-faction-header:hover { background: rgba(255,255,255,0.06); }
.lpstore-corp-faction-count { color: var(--text-mute); font-size: 13px; font-weight: 600; }
.lpstore-corp-faction-chevron { width: 15px; height: 15px; margin-left: auto; color: var(--text-mute); flex-shrink: 0; transition: transform 0.15s ease; }
.lpstore-corp-faction-chevron.open { transform: rotate(90deg); }
.lpstore-corp-faction-rows { background: rgba(0,0,0,0.15); }
.lpstore-corp-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 9px 14px 9px 28px; font-size: 16px; text-align: left; cursor: pointer;
  border-top: 1px solid rgba(255,255,255,0.03);
}
.lpstore-corp-row:hover { background: rgba(255,255,255,0.06); }
.lpstore-corp-row-active { background: rgba(var(--accent-rgb), 0.14); }
/* Real corp logo (js/lpstore.js lpCorpLogoHTML), shared by every corp row in this file (corp
   picker, LP Owned, Find Item). The wrap's background is a fixed neutral tone, NOT the corp's own
   color - a good many corp logo PNGs have real transparent regions around the emblem, and an
   underlying colored circle showed straight through those as a distracting tint even on a logo
   that loaded fine (confirmed report: "strange blue background" - Caldari-faction corps' assigned
   color is blue, and Caldari corps are exactly what was on screen). The corp's own color is only
   ever applied (via JS, onerror) as a plain fallback background for a logo that fails to load
   entirely - never layered under a real one. Sized up a good deal from the original small dot-like
   22px - large enough that the emblem itself is actually legible, not just a colored blob. */
.lpstore-corp-logo-wrap { width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0; overflow: hidden; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.06); }
.lpstore-corp-logo { width: 100%; height: 100%; object-fit: cover; }
.lpstore-corp-row-name { color: var(--text); font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lpstore-corp-row-faction { font-size: 13px; font-weight: 600; flex-shrink: 0; }

/* Find Item reverse-search results (js/lpstore.js lpItemSearchGroupHTML/lpItemSearchCorpRowHTML) -
   grouped by item (one header, shown once, per distinct item found) with every corp that offers it
   nested underneath as its own row - replacing an earlier flat per-offer-row version that repeated
   the item's own name on every single row ("no point to repeat the name over and over again"). This
   whole area is a full-width main-content section now (#lpstore-item-search-area in lpstore.html),
   not a small popover, so nothing here needs the popover's own width/scroll constraints. */
.lpstore-item-search-group + .lpstore-item-search-group { margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--glass-border); }
.lpstore-item-search-group-header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.lpstore-item-search-group-icon { width: 36px; height: 36px; border-radius: var(--radius-input); background: rgba(255,255,255,0.06); flex-shrink: 0; object-fit: contain; }

/* Autocomplete suggestions (js/lpstore.js lpItemSearchSuggestionRowHTML) - one row per distinct
   item as you type, shown in #lpstore-item-search-suggestions (reuses .lpstore-corp-popover for
   position/sizing, same anchored-dropdown-under-the-input treatment as the corp picker). Picking
   one is what actually populates the results area below - see filterLPItemSearchResults vs
   pickLPItemSearchSuggestion's own comments for why those are two separate functions now. */
.lpstore-item-search-suggestion-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; text-align: left; cursor: pointer; font-size: 14px;
  border-top: 1px solid rgba(255,255,255,0.03);
}
.lpstore-item-search-suggestion-row:first-child { border-top: none; }
.lpstore-item-search-suggestion-row:hover { background: rgba(255,255,255,0.06); }
.lpstore-item-search-suggestion-icon { width: 28px; height: 28px; border-radius: var(--radius-input); background: rgba(255,255,255,0.06); flex-shrink: 0; object-fit: contain; }
.lpstore-item-search-group-name { font-weight: 700; font-size: 17px; color: var(--text); }
.lpstore-item-search-group-count { margin-left: auto; font-size: 12.5px; color: var(--text-mute); flex-shrink: 0; }
.lpstore-item-search-group-rows { display: flex; flex-direction: column; gap: 6px; }
/* Corps you already have enough LP with float to the top of their item's group (sorted in
   lpItemSearchGroupHTML) and get a faint accent tint here so that's visible at a glance, not just
   from position - matches the "corporations I have the LP for it to show up at the top" request. */
.lpstore-item-search-corp-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 9px 14px; border-radius: var(--radius-input); text-align: left; cursor: pointer;
  background: rgba(255,255,255,0.02); font-size: 15px;
}
.lpstore-item-search-corp-row:hover { background: rgba(255,255,255,0.07); }
.lpstore-item-search-corp-row-affordable { background: rgba(var(--accent-rgb), 0.10); }
.lpstore-item-search-corp-row-affordable:hover { background: rgba(var(--accent-rgb), 0.16); }
/* Cost line + Est. ISK/LP line, stacked and right-aligned, so the row reads: corp name on the
   left, everything about the deal on the right. */
.lpstore-item-search-corp-name-wrap { display: flex; flex-direction: column; flex: 1; min-width: 0; gap: 2px; }
.lpstore-item-search-bpc-runs { font-size: 10.5px; color: var(--text-mute); white-space: nowrap; }
.lpstore-item-search-corp-row-stats { margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }
.lpstore-item-search-corp-row-cost { font-size: 13.5px; font-family: 'IBM Plex Mono', monospace; white-space: nowrap; }
.lpstore-item-search-isk { color: var(--cost); }
.lpstore-item-search-lp { color: #c084fc; }
.lpstore-item-search-profit { font-size: 12.5px; font-weight: 700; font-family: 'IBM Plex Mono', monospace; white-space: nowrap; }
.lpstore-item-search-type-badge { font-size: 9px; font-family: var(--font-mono, monospace); font-weight: 700; border-radius: 4px; padding: 2px 6px; flex-shrink: 0; }
.lpstore-item-search-type-bpc { background: rgba(192,132,252,0.15); color: #c084fc; }
.lpstore-item-search-type-item { background: rgba(56,189,248,0.15); color: #38bdf8; }
.lpstore-item-search-lp-badge { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 999px; white-space: nowrap; flex-shrink: 0; }
.lpstore-item-search-lp-ok { background: rgba(var(--accent-rgb), 0.18); color: var(--accent); }
.lpstore-item-search-lp-short { background: rgba(255,255,255,0.06); color: var(--text-mute); }

.flyout-panel input, .flyout-panel select {
  border-radius: var(--radius-input) !important;
  background: rgba(0,0,0,0.28) !important;
  border-color: var(--glass-border) !important;
  color: var(--text) !important;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.flyout-panel input:focus, .flyout-panel select:focus {
  border-color: var(--accent) !important;
  box-shadow: 0 0 0 2px rgba(var(--accent-rgb), 0.18);
}

select option { background-color: #191411; color: var(--text); padding: 6px 10px; }
select option:checked, select option:hover { background-color: var(--accent); color: #201206; }

/* Custom dropdown (js/custom-select.js): the native <option> popup only lets CSS touch background-
   color/color/padding - the popup frame (border, radius, shadow) is OS chrome and can't be themed,
   which is why the rules above alone can never make it "fit the design". js/custom-select.js hides
   the real <select> (kept for value/onchange wiring) and stands up this fully-themed replacement
   next to it - one component, so every dropdown across all three pages looks the same. */
.csel-native-hidden {
  position: absolute !important;
  width: 1px !important; height: 1px !important;
  opacity: 0 !important; pointer-events: none !important;
  overflow: hidden !important;
}
.csel-trigger {
  display: flex !important;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: rgba(255,255,255,0.035) !important;
  border: none !important;
  border-bottom: 1.5px solid rgba(255,255,255,0.08) !important;
  border-radius: var(--radius-sm) !important;
  padding: 6px 10px !important;
  /* !important because custom-select.js copies the source <select>'s className onto this trigger
     too, and one of those classes is often .lp-badge - whose own line-height:1.6 (meant for a plain
     one-line text badge with 2px vertical padding) otherwise rides along here and inflates the label
     span's own box well past its actual glyph height. align-items:center then centers that oversized
     box correctly, but the text *inside* it still sits high in the extra leading - reading as
     "not centered" even though the flex alignment itself is fine. Collapsing line-height back down
     removes the excess leading so the glyphs fill their box and centering actually looks centered. */
  line-height: 1 !important;
  /* Sensible fallback ONLY - not !important, and font-family only (not the `font` shorthand, which
     also resets size/weight/line-height). custom-select.js copies the source <select>'s className
     AND inline style onto this trigger, so whatever font-size/color that select actually specified
     (Tailwind utility class or inline style) is what should render - this just normalizes the
     button-element default font-family for triggers that specified nothing of their own, rather
     than silently discarding every select's intended text size the way `font: inherit` + a bare
     !important color was doing app-wide. */
  color: var(--text);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  outline: none;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.csel-trigger:hover { background: rgba(255,255,255,0.06) !important; }
.csel-trigger.is-open, .csel-trigger:focus-visible {
  border-bottom-color: var(--accent) !important;
  background: rgba(255,255,255,0.06) !important;
}
.csel-trigger[disabled] { opacity: 0.5; cursor: not-allowed; }
.csel-trigger-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csel-trigger-chevron {
  flex-shrink: 0; width: 13px; height: 13px;
  stroke: var(--text-soft);
  transition: transform 0.15s ease, stroke 0.15s ease;
}
.csel-trigger.is-open .csel-trigger-chevron { transform: rotate(180deg); stroke: var(--accent); }

.csel-panel {
  position: fixed;
  z-index: 9999;
  background: var(--glass-fill);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--glass-shadow);
  padding: 4px;
  max-height: 280px;
  overflow-y: auto;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.csel-panel.is-open { opacity: 1; transform: translateY(0); }
.csel-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 12px;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}
.csel-option:hover { background: rgba(var(--accent-rgb),0.14); color: var(--accent); }
.csel-option.is-selected { color: var(--accent); font-weight: 600; }
.csel-option[aria-disabled="true"] { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
.csel-empty { padding: 10px; font-size: 12px; font-style: italic; text-align: center; color: var(--text-mute); }

/* Toast notifications (js/config.js showToast()) - a real on-screen signal for failures that
   previously only hit the console or, worse, got overwritten by a false success message. */
#toast-stack {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 10000;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  max-width: 360px;
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  background: var(--glass-fill);
  border: 1px solid var(--glass-border);
  border-left: 3px solid var(--text-mute);
  border-radius: var(--radius-card);
  box-shadow: var(--glass-shadow);
  padding: 11px 12px;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--text);
  display: flex;
  align-items: flex-start;
  gap: 9px;
  opacity: 0;
  transform: translateX(16px);
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast.is-visible { opacity: 1; transform: translateX(0); }
.toast-icon { flex-shrink: 0; width: 15px; height: 15px; margin-top: 1px; stroke: currentColor; fill: none; }
.toast-error { border-left-color: var(--red); }
.toast-error .toast-icon { color: var(--red); }
.toast-success { border-left-color: var(--accent); }
.toast-success .toast-icon { color: var(--accent); }
.toast-message { flex: 1; min-width: 0; }
.toast-close {
  flex-shrink: 0;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  font-size: 13px;
  line-height: 1;
  color: var(--text-mute);
}
.toast-close:hover { color: var(--text); }
.toast-action-btn {
  flex-shrink: 0;
  cursor: pointer;
  background: rgba(var(--accent-rgb),0.16);
  border: 1px solid rgba(var(--accent-rgb),0.3);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
  transition: background 0.15s ease;
}
.toast-action-btn:hover { background: rgba(var(--accent-rgb),0.28); }

/* Inline SVG icons (window.svgIcon / <svg class="ico">) that replaced the old emoji glyphs in
   button labels, badges and status text. Sized in `em` so they track the text they sit next to;
   `flex:0 0 auto` keeps them from shrinking in the many flex button/badge rows, and the negative
   `vertical-align` optically centers them against a text baseline in the non-flex contexts. Kept
   ABOVE .pill-check-face/.lp-chip-btn's own `svg` rules further down so those still win (12/11px)
   for their own contexts. */
body.page-calc svg.ico {
  display: inline-block; /* Tailwind's own preflight resets every <svg> to display:block (see
    tailwind-build.css: "img,svg,video,...{display:block}") - harmless for an icon sitting alone
    inside a flex row, but any icon written directly into a text string (e.g. `${svgIcon('x')} Label`
    inside a plain <span>) got forced onto its own line, since a block-level box can't sit inline with
    surrounding text. That's what made a whole run of icon+label pairs (group header dividers, the
    prereq "gear" line, Invention's "Best Option" tile) stack the icon above the text instead of
    beside it, and taller than intended as a result. */
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  vertical-align: -0.14em;
}
body.page-calc .lp-badge svg.ico { width: 11px; height: 11px; vertical-align: -1.5px; }
body.page-calc .lp-pill svg.ico { width: 13px; height: 13px; vertical-align: -2.5px; margin-right: 5px; }
body.page-calc .lp-group-header svg.ico { width: 1.05em; height: 1.05em; vertical-align: -0.16em; }

/* Pill-styled checkbox (Personal/Corp asset-source toggles, used site-wide) - a real <input
   type="checkbox"> drives it so every existing onchange="applyStockLocationFilter()"/checked read
   keeps working untouched, but it's visually hidden and a sibling face is styled off its :checked
   state instead of the native square-box look. */
body.page-calc .pill-check {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  user-select: none;
}
body.page-calc .pill-check input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}
body.page-calc .pill-check-face {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  background: rgba(255,255,255,0.06);
  color: var(--text-mute);
  transition: background 0.15s ease, color 0.15s ease;
}
body.page-calc .pill-check-face svg { width: 12px; height: 12px; stroke: currentColor; fill: none; flex-shrink: 0; }
body.page-calc .pill-check:hover .pill-check-face { background: rgba(255,255,255,0.1); color: var(--text); }
body.page-calc .pill-check input:checked + .pill-check-face {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #0a1002;
}
body.page-calc .pill-check input:focus-visible + .pill-check-face {
  box-shadow: 0 0 0 2px rgba(var(--accent-rgb),0.4);
}
/* Keyboard focus ring for the rest of the interactive vocabulary - .field-line/.csel-trigger/
   .pill-check already got one when they were built, but the original buttons/pills/nav tabs only
   ever styled :hover, so tabbing through them fell back to the browser default (or nothing). */
body.page-calc .btn-glass:focus-visible,
body.page-calc .icon-btn:focus-visible,
body.page-calc .icon-rail-btn:focus-visible,
body.page-calc .nav-tab:focus-visible,
body.page-calc .lp-pill:focus-visible,
body.page-calc .lp-chip-btn:focus-visible,
body.page-calc .toggle-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(var(--accent-rgb),0.45);
}
/* Same custom chevron the ledger/invention selects use, generalized so index.html's differently-
   styled selects (bg-black/*+border, not .field-line) get the same "obviously a dropdown" cue -
   :not([size]) excludes true multi-row listboxes like the blueprint location filter, which are
   always fully expanded already and don't need a "click to open" affordance. This rule is
   deliberately lower-specificity than the theme-unify select.field-line rules, so it only fills
   the gap on pages/selects that don't already have the fuller treatment. */
body.page-calc select:not([size]) {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ba0a8' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-size: 13px;
  padding-right: 28px !important;
  cursor: pointer;
}
/* .flyout-panel select's own background rule uses the shorthand with !important, which resets
   background-image back to none and wins over the rule above on specificity - restate the chevron
   as !important here (higher specificity than that rule too) so flyout selects keep it. */
body.page-calc .flyout-panel select:not([size]) {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ba0a8' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 8px center !important;
  background-size: 13px !important;
}

.seg-bar { display: flex; gap: 2px; height: 5px; }
.seg-bar > div { flex: 1; background: var(--glass-border-top); border-radius: 1px; }
.seg-bar > div.filled { background: var(--accent); }

.toggle-btn {
  border-radius: var(--radius-pill);
  padding: 5px 10px;
  font-family: 'Inter', sans-serif;
  font-weight: 500;
  font-size: 11px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  transition: all 0.15s ease;
  border: 1px solid var(--glass-border);
  background: rgba(255,255,255,0.04);
  color: var(--text-soft);
}
.toggle-btn svg { width: 13px; height: 13px; stroke: currentColor; flex-shrink: 0; }
.toggle-btn:hover { background: rgba(255,255,255,0.08); border-color: var(--glass-border-top); color: var(--text); }
.toggle-btn-active-accent {
  background: linear-gradient(160deg, rgba(var(--accent-rgb),0.22), rgba(var(--accent-rgb),0.06));
  border-color: rgba(var(--accent-rgb),0.4);
  color: var(--copper-200);
}
.toggle-btn-active-buy {
  background: linear-gradient(160deg, rgba(var(--buy-rgb),0.22), rgba(var(--buy-rgb),0.06));
  border-color: rgba(var(--buy-rgb),0.4);
  color: var(--blue-300);
}

/* ============================================================
   Compatibility layer - JS-generated markup (tree node cards, ledger job cards, invention
   comparison rows, BOM lists) is built in app.js/ledger.js/invention.js/esi.js as plain Tailwind
   utility classes, ~400 occurrences across 5 files. Rather than hand-editing every template
   string, every Tailwind class those templates already use is redirected here onto the design
   tokens above - colors, corner radius, and shadow all flow through automatically. Attribute
   "contains" selectors ([class*="..."]) catch every opacity-suffixed variant (e.g. bg-green-600
   AND bg-green-600/80) in one rule without needing to enumerate each exact alpha used.
   ============================================================ */

/* ---- shape: radius + shadow utilities used throughout static HTML and JS templates ---- */
.rounded { border-radius: var(--radius-sm) !important; }
.rounded-md { border-radius: var(--radius-btn) !important; }
.rounded-lg { border-radius: var(--radius-panel) !important; }
.rounded-xl { border-radius: var(--radius-card) !important; }
.rounded-b-md { border-radius: 0 0 var(--radius-btn) var(--radius-btn) !important; }
.shadow, .shadow-sm, .shadow-md, .shadow-lg, .shadow-xl, .shadow-2xl {
  box-shadow: var(--glass-shadow) !important;
}

/* ---- copper (orange/amber) - coal-neutral rule: copper is reserved for actual button fills
   (bg-orange-*) and nothing else. Text and borders that used to carry a permanent copper tint
   (section labels, panel borders, badges) go neutral gray instead - that permanent tinting
   everywhere was exactly the "leftover orange clutter" that needed to go. Hover states keep a
   light touch of accent since that's transient interaction feedback, not standing chrome. ---- */
.text-orange-100, .text-amber-100 { color: var(--text) !important; }
.text-orange-200, .text-amber-200 { color: var(--text) !important; }
.text-orange-300, .text-amber-300 { color: var(--text-soft) !important; }
.text-orange-400, .text-amber-400 { color: var(--text-soft) !important; }
/* Same leftover-color cleanup, for Tailwind's default slate gray this time - text-slate-500
   specifically also failed WCAG AA contrast (4.2:1 against the page background, under the 4.5:1
   minimum), which routing it to --text-soft (7.6:1) fixes as a side effect. */
.text-slate-200, .text-slate-300 { color: var(--text) !important; }
.text-slate-400, .text-slate-500 { color: var(--text-soft) !important; }
/* text-white had no compat rule at all - every other color utility on this page maps onto the
   active theme, so this was the one spot that stayed literal white regardless of theme choice. */
.text-white { color: var(--text) !important; }
.bg-orange-400, .bg-amber-400 { background: var(--copper-400) !important; }
.bg-orange-500, [class^="bg-orange-500/"], [class*=" bg-orange-500/"] {
  background: linear-gradient(135deg, var(--accent), var(--accent-2)) !important;
}
[class*="hover:bg-orange-500/"]:hover { background: rgba(var(--accent-rgb),0.16) !important; }
.border-orange-500, [class^="border-orange-500/"], [class*=" border-orange-500/"] { border-color: var(--glass-border) !important; }
[class*="hover:border-orange-500"]:hover { border-color: rgba(var(--accent-rgb),0.5) !important; }
.bg-orange-600 { background: var(--copper-600) !important; }
[class*="hover:bg-orange-600"]:hover { background: var(--copper-500) !important; }
.border-orange-600, [class^="border-orange-600/"], [class*=" border-orange-600/"] { border-color: var(--glass-border) !important; }
.bg-orange-700, .bg-amber-700 { background: var(--copper-700) !important; }
[class*="hover:bg-orange-700"]:hover, [class*="hover:bg-amber-600"]:hover { background: var(--copper-600) !important; }
.border-orange-700, [class^="border-orange-700/"], [class*=" border-orange-700/"] { border-color: var(--glass-border) !important; }
.bg-orange-800 { background: var(--copper-800) !important; }
[class*="hover:bg-orange-800"]:hover { background: var(--copper-700) !important; }
.bg-orange-900, [class^="bg-orange-900/"], [class*=" bg-orange-900/"] { background: rgba(255,255,255,0.08) !important; }
.bg-orange-950, [class^="bg-orange-950/"], [class*=" bg-orange-950/"] { background: rgba(255,255,255,0.06) !important; }
.border-amber-500, [class^="border-amber-500/"], [class*=" border-amber-500/"] { border-color: var(--glass-border) !important; }
[class^="border-amber-600/"], [class*=" border-amber-600/"] { border-color: var(--glass-border) !important; }
[class*="hover:border-amber-400"]:hover { border-color: rgba(var(--accent-rgb),0.5) !important; }
.bg-amber-900, [class^="bg-amber-900/"], [class*=" bg-amber-900/"] { background: rgba(255,255,255,0.08) !important; }
.bg-amber-950, [class^="bg-amber-950/"], [class*=" bg-amber-950/"] { background: rgba(255,255,255,0.06) !important; }
[class*="ring-orange-400"] { --tw-ring-color: rgba(var(--accent-rgb),0.7) !important; }
.accent-purple-500 { accent-color: var(--violet) !important; }
.accent-cyan-500 { accent-color: var(--blue) !important; }
/* focus:border-* variants aren't caught by the static class selectors above (Tailwind's CDN
   engine generates its own raw-color :focus rule for these, injected after this stylesheet, so
   it was winning and showing a literal un-migrated orange/cyan/purple/amber border on focus
   regardless of page or accent color). */
[class*="focus:border-orange-500"]:focus { border-color: var(--accent) !important; }
[class*="focus:border-amber-500"]:focus { border-color: var(--accent) !important; }
[class*="focus:border-cyan-500"]:focus { border-color: var(--blue) !important; }
[class*="focus:border-purple-500"]:focus { border-color: var(--violet) !important; }

/* ---- blue (cyan) ---- */
.text-cyan-100 { color: #dbe8fb !important; }
.text-cyan-200 { color: var(--blue-300) !important; }
.text-cyan-300 { color: var(--blue-300) !important; }
.text-cyan-400 { color: var(--blue-400) !important; }
.bg-cyan-700, [class^="bg-cyan-700/"], [class*=" bg-cyan-700/"] { background: linear-gradient(135deg, var(--blue-500), var(--blue-700)) !important; }
[class*="hover:bg-cyan-600"]:hover { background: var(--blue-500) !important; }
[class*="hover:bg-cyan-700"]:hover { background: var(--blue-600) !important; }
.bg-cyan-800 { background: var(--blue-700) !important; }
.bg-cyan-900, [class^="bg-cyan-900/"], [class*=" bg-cyan-900/"] { background: rgba(var(--blue-rgb),0.6) !important; }
.bg-cyan-950, [class^="bg-cyan-950/"], [class*=" bg-cyan-950/"] { background: rgba(var(--blue-rgb),0.32) !important; }
.border-cyan-400, .border-cyan-500, [class^="border-cyan-400/"], [class*=" border-cyan-400/"], [class^="border-cyan-500/"], [class*=" border-cyan-500/"] { border-color: rgba(var(--blue-rgb),0.5) !important; }
.border-cyan-600, [class^="border-cyan-600/"], [class*=" border-cyan-600/"] { border-color: rgba(var(--blue-rgb),0.4) !important; }
[class*="ring-cyan-400"] { --tw-ring-color: rgba(var(--blue-rgb),0.7) !important; }

/* ---- violet (purple) ---- */
.text-purple-100 { color: var(--violet-100) !important; }
.text-purple-300 { color: var(--violet-300) !important; }
.text-purple-400 { color: var(--violet-400) !important; }
[class*="hover:text-purple-200"]:hover, [class*="hover:text-purple-300"]:hover { color: var(--violet-300) !important; }
.bg-purple-500, .bg-purple-600, [class^="bg-purple-600/"], [class*=" bg-purple-600/"] { background: linear-gradient(135deg, var(--violet-500), var(--violet-700)) !important; }
[class*="hover:bg-purple-500"]:hover { background: var(--violet-400) !important; }
[class*="hover:bg-purple-600"]:hover { background: var(--violet-500) !important; }
.bg-purple-700, [class^="bg-purple-700/"], [class*=" bg-purple-700/"] { background: var(--violet-600) !important; }
[class*="hover:bg-purple-700"]:hover { background: var(--violet-500) !important; }
.bg-purple-800, [class^="bg-purple-800/"], [class*=" bg-purple-800/"] { background: var(--violet-700) !important; }
[class*="hover:bg-purple-800"]:hover { background: var(--violet-600) !important; }
.bg-purple-950, [class^="bg-purple-950/"], [class*=" bg-purple-950/"] { background: rgba(var(--violet-rgb),0.42) !important; }
.border-purple-500, [class^="border-purple-500/"], [class*=" border-purple-500/"] { border-color: rgba(var(--violet-rgb),0.45) !important; }
.border-purple-600, [class^="border-purple-600/"], [class*=" border-purple-600/"] { border-color: rgba(var(--violet-rgb),0.35) !important; }
.border-purple-800, [class^="border-purple-800/"], [class*=" border-purple-800/"] { border-color: rgba(var(--violet-rgb),0.4) !important; }
[class*="hover:border-purple-500"]:hover { border-color: rgba(var(--violet-rgb),0.5) !important; }

/* ---- green (profit) ---- */
.text-green-300 { color: var(--green-300) !important; }
.text-green-400 { color: var(--green-400) !important; }
.bg-green-400 { background: var(--green-400) !important; }
.bg-green-600 { background: var(--green-600) !important; }
.bg-green-700, [class^="bg-green-700/"], [class*=" bg-green-700/"] { background: var(--green-700) !important; }
.bg-green-800, [class^="bg-green-800/"], [class*=" bg-green-800/"] { background: var(--green-700) !important; }
[class*="hover:bg-green-700"]:hover { background: var(--green-600) !important; }
.bg-green-950, [class^="bg-green-950/"], [class*=" bg-green-950/"] { background: rgba(var(--green-rgb),0.28) !important; }
.border-green-500, [class^="border-green-500/"], [class*=" border-green-500/"] { border-color: rgba(var(--green-rgb),0.5) !important; }
.border-green-600, [class^="border-green-600/"], [class*=" border-green-600/"] { border-color: rgba(var(--green-rgb),0.4) !important; }
.border-green-700, [class^="border-green-700/"], [class*=" border-green-700/"] { border-color: rgba(var(--green-rgb),0.4) !important; }
[class*="hover:border-green-400"]:hover, [class*="hover:border-green-500"]:hover { border-color: var(--green-400) !important; }

/* ---- red (loss / destructive) ---- */
.text-red-300 { color: var(--red-300) !important; }
.text-red-400 { color: var(--red-400) !important; }
.text-red-500 { color: var(--red-500) !important; }
[class*="hover:text-red-300"]:hover, [class*="hover:text-red-400"]:hover { color: var(--red-400) !important; }
.bg-red-500 { background: var(--red-500) !important; }
.bg-red-800, [class^="bg-red-800/"], [class*=" bg-red-800/"] { background: #8f3a35 !important; }
.bg-red-950, [class^="bg-red-950/"], [class*=" bg-red-950/"] { background: rgba(var(--red-rgb),0.32) !important; }
[class*="hover:bg-red-800"]:hover { background: #a3423c !important; }
.border-red-800, [class^="border-red-800/"], [class*=" border-red-800/"] { border-color: rgba(var(--red-rgb),0.45) !important; }

/* ---- emerald (aliased to the green ramp) ---- */
.text-emerald-300 { color: var(--green-300) !important; }
.bg-emerald-700, [class^="bg-emerald-700/"], [class*=" bg-emerald-700/"] { background: var(--green-700) !important; }
[class*="hover:bg-emerald-600"]:hover { background: var(--green-600) !important; }
.bg-emerald-950, [class^="bg-emerald-950/"], [class*=" bg-emerald-950/"] { background: rgba(var(--green-rgb),0.35) !important; }
.border-emerald-600, [class^="border-emerald-600/"], [class*=" border-emerald-600/"] { border-color: rgba(var(--green-rgb),0.4) !important; }

/* ---- literal bg-[#hex]/border-[#hex] classes from the pre-redesign palette (index.html /
   ledger.html / invention.html static shells) ---- */
[class*="bg-[#3a4a4c]"] { background: rgba(255,255,255,0.12) !important; }
[class*="bg-[#202a2e]"] { background: var(--glass-fill) !important; }
[class*="bg-[#0a0d0e]"] { background: var(--glass-fill) !important; }
[class*="bg-[#030405]"] { background: rgba(0,0,0,0.45) !important; }
[class*="bg-[#070b0f]"] { background: var(--bg-0) !important; }
[class*="bg-[#0c1318]"] { background: var(--glass-fill) !important; }
[class*="bg-[#0d1922]"] { background: var(--glass-fill) !important; }
[class*="bg-[#0a0f14]"] { background: transparent !important; }
[class*="bg-[#05080c]"] { background: rgba(0,0,0,0.3) !important; }
[class*="bg-[#1e3348]"] { background: rgba(255,255,255,0.1) !important; }
[class*="border-[#1e3348]"] { border-color: var(--glass-border) !important; }
[class*="divide-[#1e3348]"] > :not([hidden]) ~ :not([hidden]) { border-color: var(--glass-border) !important; }
[class*="text-[#c8d8e8]"] { color: var(--text-soft) !important; }
[class*="border-[#3a3025]"] { border-color: var(--glass-border) !important; }
[class*="bg-[#0d2818]"] { background: rgba(var(--green-rgb),0.24) !important; }
[class*="text-[#e85555]"] { color: var(--red) !important; }
[class*="text-[#a8d4f0]"] { color: var(--blue-300) !important; }

/* ============================================================
   Foxstock-derived "gradient bento" system, scoped to body.page-calc - applied to all three pages
   (the class name is a holdover from when this started as an index.html-only experiment; it now
   just means "the current site theme", not "calculator page"). The coal-neutral rules above this
   block are effectively the pre-Foxstock default and stay in the cascade as a fallback for any
   page/element that doesn't carry .page-calc.
   Palette: near-black canvas + one lime-green accent used the way Foxstock uses it - brand mark,
   primary actions, "up"/profit, and node highlight all read off the same color - plus coral for
   "down"/loss. Cards drop the translucent blur+border in favor of a subtle diagonal gradient fill
   and zero border ("treatment B", chosen from a 9-way card-frame comparison) - depth comes from
   the fill plus a soft drop shadow only, no glass sheen. Because nearly everything below (tree
   node cards, BOM sidebar, flyouts, stat tiles, and the compat layer's orange/green/red remaps)
   reads its colors and shape off the custom-property tokens set here, redefining the tokens
   re-skins almost the whole page for free - only the genuinely structural differences (stat tiles
   as separate cards instead of one divided strip, header/icon-rail going chrome-less, the tree
   diagram's own canvas becoming the hero surface instead of a flat tile with a watermark) need
   their own rules below.
   ============================================================ */
body.page-calc {
  --bg-0: #08090a;
  --text: #f2f3f5;
  --text-soft: #9ba0a8;
  /* #6b7078 (4.0:1 against --bg-0) failed WCAG AA's 4.5:1 minimum for normal text - lightened to
     5.1:1 while keeping the same neutral gray-blue hue. */
  --text-mute: #7d8288;

  /* Lime ramp dimmed from the original neon peak (was #c8ff4d, 100% saturation/65% lightness) to a
     calmer #9de137 (74%/55%) - same hue, still unmistakably "lime", but no longer bright enough to
     read as a highlighter under low ambient light. One ramp, referenced by --accent/--green/--copper
     alike so dimming it here re-dims the whole app. */
  --accent: #9de137;
  --accent-2: #6fa919;
  --accent-rgb: 157,225,55;

  --green: #9de137;
  --green-rgb: 157,225,55;
  --green-300: #c2ec83; --green-400: #9de137; --green-500: #87cc1e;
  --green-600: #6fa919; --green-700: #588514; --green-800: #43660f;

  --red: #e8845a;
  --red-rgb: 232,132,90;
  --red-300: #f3c0a8; --red-400: #e8845a; --red-500: #d9713f;

  --copper-100: #e9f8d3; --copper-200: #d8f3af; --copper-300: #c2ec83;
  --copper-400: #9de137; --copper-500: #9de137; --copper-600: #87cc1e;
  --copper-700: #6fa919; --copper-800: #588514; --copper-900: #43660f; --copper-950: #2f470b;

  --glass-fill: linear-gradient(155deg, #14161a, #0b0c0d);
  --glass-border: rgba(255,255,255,0.06);
  --glass-border-top: rgba(255,255,255,0.06);
  --glass-shadow: 0 14px 32px rgba(0,0,0,0.4);
  --blur: 0px;

  --radius-sm: 8px;
  --radius-btn: 10px;
  --radius-panel: 18px;
  --radius-card: 20px;
}

body.page-calc {
  background: radial-gradient(ellipse 1000px 560px at 50% -10%, rgba(var(--accent-rgb),.1), transparent 60%), var(--bg-0);
}

/* ============================================================
   Selectable color themes - swap --accent/--accent-2/--accent-rgb, the --copper-* ramp (which the
   entire orange/amber Tailwind-utility compat layer above already reads from), AND the neutral
   background/text tokens (--bg-0/--text/--text-soft/--text-mute/--glass-fill/--glass-border), each
   given a subtle hue bias toward that theme's accent rather than staying one fixed graphite for
   every theme - a pure grey reads as unconsidered, a grey nudged toward the accent reads as chosen.
   Every neutral below was generated by hue-rotating the default Lime theme's neutrals at matched
   saturation/lightness (not picked by eye), so each theme's text contrast against its own --bg-0
   is verified >= the Lime baseline's 4.5:1+ WCAG AA minimum, not just assumed to still pass.
   Everything that currently renders in copper/lime already re-skins for free through the existing
   compat-layer chain (including the header brand chips, the diagram/queue viewport's ambient glow,
   dropdown panels, and, via .theme-unify, the ledger/invention pages' purple badges - all already
   resolve through these same tokens, not hardcoded hex).
   --green/--red/--blue intentionally do NOT vary by theme - they're semantic (profit/loss, buy vs
   build), not brand color, and staying constant is what makes them readable as "always means the
   same thing" regardless of which theme is active. Selector is [data-theme] on <html> (not a class
   on <body>) so a tiny inline script in <head> can set it before first paint, avoiding a flash of
   the default theme while the deferred scripts are still loading.
   ============================================================ */
/* Every non-default theme also resets --green/--red back to a genuine green/red (the base :root's
   values) - the default Lime theme deliberately unifies --green with --accent since lime IS a shade
   of green (see the comment on body.page-calc above), but that unification doesn't hold for these
   themes' accent hues, so left alone it would leak through as "profit is always lime-green" no
   matter which accent color is picked - exactly the kind of stray meaning drift these three-var
   overrides exist to prevent. */
html[data-theme="copper"] body.page-calc {
  --accent: #d3915a; --accent-2: #a86336; --accent-rgb: 211,145,90;
  --copper-100: #f3ddc7; --copper-200: #ecc9a3; --copper-300: #e3b17d; --copper-400: #d3915a;
  --copper-500: #d3915a; --copper-600: #b97847; --copper-700: #a86336; --copper-800: #855231;
  --copper-900: #5f3a22; --copper-950: #3d2515;
  --green: #4cc491; --green-rgb: 76,196,145;
  --green-300: #a3e8cb; --green-400: #4cc491; --green-500: #3fae7c; --green-600: #349a6c; --green-700: #2b7f59; --green-800: #235f45;
  --red: #dd6b64; --red-rgb: 221,107,100;
  --red-300: #f0b3ae; --red-400: #dd6b64; --red-500: #cf5a53;
  --bg-0: #0a0908;
  --text: #f5f3f2; --text-soft: #aaa199; --text-mute: #8a827b;
  --glass-fill: linear-gradient(155deg, #1a1612, #0e0d0c);
  --glass-border: rgba(255,235,215,0.07); --glass-border-top: rgba(255,235,215,0.07);
}
/* A genuine cyan/teal, deliberately NOT reusing --blue's exact periwinkle value - index.html reads
   --blue directly (not through theme-unify) as the functional "buy mode" node color, so an accent
   that exactly matched it would flatten that distinction into the general UI chrome on this theme. */
html[data-theme="cyan"] body.page-calc {
  --accent: #22c3d4; --accent-2: #1a96a3; --accent-rgb: 34,195,212;
  --copper-100: #c9f2f5; --copper-200: #a3e8ec; --copper-300: #6dd8e0; --copper-400: #3cc7d1;
  --copper-500: #22c3d4; --copper-600: #1aa3b2; --copper-700: #158490; --copper-800: #106670;
  --copper-900: #0b474e; --copper-950: #072e33;
  --green: #4cc491; --green-rgb: 76,196,145;
  --green-300: #a3e8cb; --green-400: #4cc491; --green-500: #3fae7c; --green-600: #349a6c; --green-700: #2b7f59; --green-800: #235f45;
  --red: #dd6b64; --red-rgb: 221,107,100;
  --red-300: #f0b3ae; --red-400: #dd6b64; --red-500: #cf5a53;
  --bg-0: #080a0a;
  --text: #f2f5f5; --text-soft: #99a7aa; --text-mute: #7b878a;
  --glass-fill: linear-gradient(155deg, #12191a, #0c0e0e);
  --glass-border: rgba(210,245,250,0.07); --glass-border-top: rgba(210,245,250,0.07);
}
/* Same reasoning as Cyan above, applied to --violet (index.html's "reaction mode" node color) - a
   more saturated blue-violet instead of reusing --violet's exact softer tone. */
html[data-theme="violet"] body.page-calc {
  --accent: #a374e0; --accent-2: #7c4fc4; --accent-rgb: 163,116,224;
  --copper-100: #ede3fa; --copper-200: #ddc9f5; --copper-300: #c9a8ee; --copper-400: #b58ce5;
  --copper-500: #a374e0; --copper-600: #8f5cd0; --copper-700: #7c4fc4; --copper-800: #603a9e;
  --copper-900: #452a73; --copper-950: #2e1c4d;
  --green: #4cc491; --green-rgb: 76,196,145;
  --green-300: #a3e8cb; --green-400: #4cc491; --green-500: #3fae7c; --green-600: #349a6c; --green-700: #2b7f59; --green-800: #235f45;
  --red: #dd6b64; --red-rgb: 221,107,100;
  --red-300: #f0b3ae; --red-400: #dd6b64; --red-500: #cf5a53;
  --bg-0: #09080a;
  --text: #f3f2f5; --text-soft: #a099aa; --text-mute: #817b8a;
  --glass-fill: linear-gradient(155deg, #15121a, #0d0c0e);
  --glass-border: rgba(230,220,250,0.07); --glass-border-top: rgba(230,220,250,0.07);
}

/* Theme picker: a small icon-btn trigger + popover of swatches, same spot in the header on all
   three pages (right before the SSO login button). */
.theme-menu-wrap { position: relative; }
.theme-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 50;
  background: var(--glass-fill), var(--bg-0);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-panel);
  box-shadow: var(--glass-shadow);
  padding: 8px;
  display: flex;
  gap: 6px;
}
.theme-menu.hidden { display: none; }

/* Required proprietary notice (EVE Online Developer License Agreement, section 7.1
   "Proprietary Notices" - Developer must retain a notice substantially in this form). CCP hf. has
   rebranded to Fenris Creations (confirmed live on eveonline.com's own site footer, which now reads
   "EVE Online(r) and Fenris Creations(tm)... are trademarks of Fenris Creations" / "(c) Fenris
   Creations. All rights reserved.") - this mirrors that exact current wording, shortened to fit a
   single thin line. The bar's height comes from padding + line-height, not font size - so this
   stays at the same readable 10.5px caption size used everywhere else (.stat-label etc.) and gets
   thin purely by dropping vertical padding to 0 and tightening line-height, instead of shrinking
   text (which was tried and made it hard to read without actually shrinking the bar much).
   The remaining dead space below the text was body's own outer canvas padding
   (clamp(16px,0.9vw,28px) on all sides, for the floating-glass-panel gap look) - this footer is
   the last item in that flex column, so it inherited that same inset as trailing space below the
   text. Cancelled with a negative bottom margin equal to that same clamp() (kept as an actual
   clamp(), not a fixed px, so it still fully cancels at every viewport width), then a small
   deliberate 4px put back so the text isn't flush against the literal window edge. */
.site-legal-footer {
  flex-shrink: 0;
  padding: 0 16px 4px;
  margin-bottom: calc(-1 * clamp(16px, 0.9vw, 28px));
  font-size: 10.5px;
  line-height: 1.15;
  color: var(--text-mute);
  text-align: center;
}
.site-legal-footer a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: rgba(255,255,255,0.25);
  text-underline-offset: 2px;
}
.site-legal-footer a:hover { color: var(--text); text-decoration-color: currentColor; }

/* Community & Support trigger - a labeled .btn-glass (the same "loud, bright, primary" treatment
   as any other true call-to-action button), not a bare 30px icon glyph with only a hover tooltip to
   say what it does - reported directly as too small/unclear to invite a click. The pulse is a soft,
   slow attention ring for a first-time visitor only (removed the moment they open the panel once,
   or on any later page load once they have - see toggleCommunityMenu/initCommunityMenuButton, JS),
   so it invites the first click without nagging every visit after. */
@keyframes community-btn-pulse-ring {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.45); }
  50% { box-shadow: 0 0 0 7px rgba(var(--accent-rgb), 0); }
}
.community-btn-pulse { animation: community-btn-pulse-ring 2.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .community-btn-pulse { animation: none; }
}

/* Community & Support panel - same toggle-panel mechanics as .theme-menu (absolute, right-anchored,
   closed by an outside click), just wider content: a Discord invite, in-game mail target, and ISK
   donation targets, instead of theme swatches. */
.community-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 50;
  width: 260px;
  background: var(--glass-fill), var(--bg-0);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-panel);
  box-shadow: var(--glass-shadow);
  padding: 14px;
}
.community-menu.hidden { display: none; }
.community-menu-title { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-mute); margin-bottom: 8px; }
.community-menu-title:not(:first-child) { margin-top: 14px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,0.06); }
.community-menu-note { font-size: 11px; color: var(--text-mute); line-height: 1.5; margin-bottom: 8px; }
.community-menu-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 0; font-size: 12px; }
.community-menu-row-label { color: var(--text-soft); }
.community-menu-row .copy-name { font-weight: 600; color: var(--text); }
.theme-swatch {
  width: 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 6px 4px;
  border-radius: var(--radius-btn);
  cursor: pointer;
  border: 1px solid transparent;
  background: transparent;
  transition: background 0.15s ease;
}
.theme-swatch:hover { background: rgba(255,255,255,0.06); }
.theme-swatch.is-active { border-color: var(--accent); background: rgba(var(--accent-rgb),0.12); }
.theme-swatch-dot {
  width: 20px; height: 20px; border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.15);
}
.theme-swatch-label { font-size: 9px; font-weight: 700; color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.04em; }
.theme-swatch.is-active .theme-swatch-label { color: var(--text); }

/* .theme-unify (ledger.html, invention.html): redirect the entire violet and blue ramps onto the
   lime accent ramp. Per-tool violet/cyan identity (purple logo badge, "Copy Multibuy", cyan
   checkboxes, etc.) was a deliberate leftover from the pre-Foxstock design, kept on purpose the
   first time this got ported here - but it's exactly what read as "two designs mixed together"
   once seen next to the calculator's all-lime language. index.html is NOT in this selector: it
   still needs real --blue/--violet for buy-mode vs. reaction-mode node accents and the parent-
   highlight glow, which are functional distinctions, not tool branding. */
body.page-calc.theme-unify {
  --violet: var(--accent); --violet-rgb: var(--accent-rgb);
  --violet-100: var(--copper-100); --violet-300: var(--copper-300); --violet-400: var(--copper-400);
  --violet-500: var(--copper-500); --violet-600: var(--copper-600); --violet-700: var(--copper-700);
  --violet-800: var(--copper-800);
  --blue: var(--accent); --blue-rgb: var(--accent-rgb); --buy: var(--accent); --buy-rgb: var(--accent-rgb);
  --blue-300: var(--copper-300); --blue-400: var(--copper-400); --blue-500: var(--copper-500);
  --blue-600: var(--copper-600); --blue-700: var(--copper-700); --blue-800: var(--copper-800);
  --blue-900: var(--copper-900);
}

/* FINAL PASS: border-[#1e3348] (and its /NN opacity variants) is the single class ledger.js and
   invention.js reach for on nearly every input, select, dropdown, and small nested box - 41
   occurrences across the two pages' markup, none of them removed by the card-level fixes above
   because those targeted specific wrapper elements, not this one pervasive class. Individually
   each border was faint, but 41 of them stacked across a form-heavy sidebar is exactly what reads
   as "still boxy/outlined" - dropping it entirely (border-radius stays wherever already set) is
   more reliable than continuing to special-case wrappers one at a time. */
body.page-calc.theme-unify [class*="border-[#1e3348]"] {
  border-color: transparent !important;
}
/* Giving every nested input/select/sub-row div its OWN subtle background (previous attempt: a
   dark rgba overlay) does not actually reduce visible "boxiness" enough, for two compounding
   reasons: (1) a rounded rectangle is a geometrically regular shape, and human vision detects its
   edge readily even at very low color contrast - subtlety alone doesn't stop it reading as "a
   box"; (2) inputs nested INSIDE an already-darkened row apply their own overlay on top of the
   row's already-darkened result, so two "identical, subtle" rgba(0,0,0,0.25) values compound into
   a visibly darker inner rectangle instead of cancelling out - same declared value, different
   rendered result, because backgrounds composite against whatever is already behind them, not
   against each other's source values.
   The actual fix: give these nested elements NO background of their own at all. With nothing to
   paint, there is no rectangle to perceive - organization comes from spacing and text color only,
   exactly like the card-level "no border, gradient fill only" treatment used everywhere else, just
   taken one level further for elements this small. */
body.page-calc.theme-unify div[class*="bg-[#070b0f]"] {
  background: transparent !important;
}

/* ============================================================
   Ledger/Invention form fields - REBUILT, not patched. Every input/select on these two pages now
   uses one of exactly two deliberate component classes instead of ad-hoc per-instance Tailwind
   combos (bg-[#0d1922]/bg-[#070b0f] + border + border-[#1e3348] + rounded), which is what made
   every previous round of fixes miss instances - there was no single pattern to fix, just dozens
   of near-identical but not-quite-identical class strings.

   .field-line: dense/secondary fields (tax %, skill level, target BPCs, filter/dropdown selects).
   A persistent underline, even with no box shape, still read as "a line I didn't ask for" -
   so now there is NOTHING painted at rest at all: no fill, no border, zero visual difference from
   plain text. The underline only appears the moment a field is actually focused, as feedback that
   you're now editing it - not as permanent decoration on every field whether you're using it or
   not. This is the only way left to guarantee nothing is visible until you interact with it.

   .field-box: the ONE prominent field per page (the item search box) - it's the primary action,
   so it keeps a soft fill, matching how the calculator treats its own primary search field. */
body.page-calc .field-line {
  background: transparent !important;
  border: none !important;
  border-bottom: 1.5px solid transparent !important;
  border-radius: 0 !important;
  transition: border-color 0.15s ease;
}
body.page-calc .field-line:focus {
  border-bottom-color: var(--accent) !important;
  outline: none;
}
/* Selects are a different case from text inputs: there's no cursor, no typing affordance, nothing
   self-evident about them being interactive - the "nothing painted until you interact" rule that's
   right for text fields just makes a <select> look like inert text. A persistent chevron plus a
   faint resting fill is the minimum needed to read as "click me to choose", without going back to
   the old bordered-box look. */
body.page-calc select.field-line {
  appearance: none;
  -webkit-appearance: none;
  background: rgba(255,255,255,0.035) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ba0a8' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E") right 6px center / 13px no-repeat !important;
  border-bottom: 1.5px solid rgba(255,255,255,0.08) !important;
  border-radius: var(--radius-sm) !important;
  padding: 6px 24px 6px 10px !important;
  cursor: pointer;
}
body.page-calc select.field-line:hover {
  background-color: rgba(255,255,255,0.06) !important;
}
body.page-calc select.field-line:focus {
  background-color: rgba(255,255,255,0.06) !important;
  border-bottom-color: var(--accent) !important;
}
body.page-calc .field-box {
  background: rgba(0,0,0,0.25) !important;
  border: none !important;
  border-radius: var(--radius-btn) !important;
  transition: box-shadow 0.15s ease;
}
body.page-calc .field-box:focus {
  box-shadow: 0 0 0 1.5px rgba(var(--accent-rgb),0.55);
  outline: none;
}

/* .field-editable: an opt-in modifier for .field-line number/text inputs where the value genuinely
   drives the math (invention success chance, skill levels, target BPCs, tax %) and blending into
   plain text at rest was actively misleading, not just understated - the same problem .field-line's
   own comment already solved for <select> (a persistent faint fill + icon) but never extended to
   text/number inputs. Adds that same resting affordance, with a pencil instead of a chevron, without
   touching the shared .field-line rule everywhere else it's used for genuinely secondary fields. */
body.page-calc .field-editable {
  background: rgba(255,255,255,0.035) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ba0a8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 20h9'/%3E%3Cpath d='M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z'/%3E%3C/svg%3E") right 4px center / 10px no-repeat !important;
  border-bottom: 1.5px solid rgba(255,255,255,0.1) !important;
  border-radius: var(--radius-sm) !important;
  /* .field-line itself sets no padding at all (fine when it was invisible at rest - nothing to
     visually misalign against) - now that this modifier gives it a real box, an unpadded left edge
     read as the number being crammed against the box's edge. Matches select.field-line's own
     padding (6px 24px 6px 10px) on the sides that aren't already spoken for by the pencil icon. */
  padding: 6px 17px 6px 10px !important;
}
body.page-calc .field-editable:hover {
  background-color: rgba(255,255,255,0.065) !important;
}
body.page-calc .field-editable:focus {
  background-color: rgba(255,255,255,0.065) !important;
  border-bottom-color: var(--accent) !important;
}

/* .num-no-spin: strips the browser's native up/down spinner buttons on a number input - meant for
   the Ledger's inline Jobs/Runs editors, which size themselves down to as few as 2-3ch to fit a
   dense row. The spinner buttons are unconditional chrome a <input type=number> always reserves
   space for regardless of the input's own CSS width, and at that width they were eating nearly all
   of it, leaving the actual digit clipped or invisible - not a sizing bug, a fixed cost this rule
   removes. The keyboard/scroll up-down-arrow increment behavior is native to the input type and
   unaffected - only the visible button chrome goes away. */
body.page-calc .num-no-spin {
  -moz-appearance: textfield;
}
body.page-calc .num-no-spin::-webkit-inner-spin-button,
body.page-calc .num-no-spin::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* ============================================================
   Shared component vocabulary (originally built for Ledger + Invention, extended to the whole
   page-calc family so any page can reuse it) - small set of classes reused everywhere instead of
   one-off Tailwind combinations. This is the actual fix for "still boxy/still has lines": every
   card is .glass-card or .lp-card (same gradient-fill, zero-border treatment), every pill/badge/
   label pulls from here, so there is exactly one visual definition of each component instead of
   dozens of near-identical hand-written variants to keep in sync across pages. */
body.page-calc .lp-card {
  background: var(--glass-fill);
  border-radius: var(--radius-card);
  box-shadow: var(--glass-shadow);
}
body.page-calc .lp-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-mute);
}
body.page-calc .lp-badge {
  display: inline-block;
  background: rgba(255,255,255,0.08);
  color: var(--text-soft);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  padding: 2px 9px;
  line-height: 1.6;
}
body.page-calc .lp-badge-accent { background: rgba(var(--accent-rgb),0.18); color: var(--accent); }
/* Job ownership tag (portrait + character name) - a dedicated class, not the generic .lp-badge above.
   .lp-badge was built for short text-only labels: tight 2px vertical padding and a 999px pill radius
   that only reads as a barely-rounded rectangle on a WIDE box like the preset selector sitting next
   to it - on a SHORT box hosting a round portrait, that same 999px radius reads as a much more
   pronounced stadium/capsule shape, clashing with the selector's own visual weight even though both
   technically share the same border-radius value. Uses --radius-input instead - the same token the
   preset <select> itself is styled with elsewhere - so the two read as the same family of shape
   regardless of how each one's radius:width ratio plays out. Asymmetric padding (tight left, roomy
   right) keeps the portrait snug against the pill's own edge while still giving the name room to
   breathe, and align-items:center on a real flex row (not an inline-block relying on line-height/
   vertical-align) keeps the portrait from ever looking "just barely fitted" no matter its exact size. */
body.page-calc .lp-owner-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(255,255,255,0.08);
  color: var(--text-soft);
  border-radius: var(--radius-input);
  padding: 3px 10px 3px 3px;
  font-size: 11px;
  font-weight: 700;
}
body.page-calc .lp-owner-pill img {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: block;
  flex-shrink: 0;
}
body.page-calc .lp-badge-danger { background: rgba(var(--red-rgb),0.18); color: var(--red); }
/* Order-type (BUY/SELL) badges need two genuinely different colors, not two shades of the same
   accent - a hardcoded blue rather than the --blue/--buy tokens, since theme-unify (ledger/
   invention) redirects both of those onto --accent for the rest of the UI and would make this
   badge collapse back to solid green there too. Buy stays lime (.lp-badge-accent) to match the
   existing buy/sell toggle buttons, where buy is already the highlighted "usually more
   profitable" choice; sell gets this blue so the two are distinguishable at a glance. */
body.page-calc .lp-badge-blue { background: rgba(106,152,222,0.18); color: #6a98de; }
/* Same reasoning as .lp-badge-blue above, same fix - hardcoded rather than var(--violet), which
   theme-unify also redirects onto --accent on the Ledger. A material acquired via an LP Store
   redemption (js/optimizers.js's 'lp' price strategy) isn't a market purchase at all, so tagging it
   BUY (the only two options this badge had before) was actively misleading, not just imprecise -
   it reads as "go buy this on the market" for something you're not buying on the market. Reuses
   --violet's own literal value (154,134,214 / #9a86d6), tying it to the same violet identity
   redemption-requirement cards already use elsewhere in the LP Store. */
body.page-calc .lp-badge-violet { background: rgba(154,134,214,0.18); color: #9a86d6; }

/* Marks the blueprint-browser row for whatever's currently loaded into the calculator - persists
   across closing/reopening the drawer (even across a reload) since it's driven by a localStorage-
   backed item_id, not just in-memory state, so "where did I leave off" survives a full page cycle. */
body.page-calc .bp-row-loaded {
  border-color: var(--accent) !important;
  background: rgba(var(--accent-rgb),0.08) !important;
}
body.page-calc .lp-pill {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 11px;
  font-weight: 700;
  background: rgba(255,255,255,0.06);
  color: var(--text-soft);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
body.page-calc .lp-pill:hover { background: rgba(255,255,255,0.1); color: var(--text); }
body.page-calc .lp-pill.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #0a1002;
}
/* An .lp-badge-accent nested inside an active pill (the Invention page's Job Queue count, e.g.)
   inherits nothing from the rule above - .lp-badge-accent sets its OWN color:var(--accent) and a
   translucent accent-tinted background, which is exactly the two colors the pill's own active-state
   gradient now uses too. The badge and its own number both read as lime-on-lime against the pill's
   lime gradient - reported directly as present but unreadable. Darkening just the badge's background
   here (its text color is untouched) restores the contrast the badge always had against its more
   usual dark card background, without changing .lp-badge-accent's appearance anywhere else it's used. */
body.page-calc .lp-pill.active .lp-badge-accent {
  background: rgba(10,16,2,0.3);
}
body.page-calc .lp-row {
  padding-top: 14px;
  margin-top: 14px;
  border-top: 1px solid rgba(255,255,255,0.05);
}
body.page-calc .lp-row:first-child { margin-top: 0; padding-top: 0; border-top: none; }
body.page-calc .lp-tile {
  background: var(--glass-fill);
  border-radius: var(--radius-card);
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
body.page-calc .lp-list-item {
  padding: 8px 12px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  border-top: 1px solid rgba(255,255,255,0.05);
  transition: background 0.15s ease;
}
body.page-calc .lp-list-item:first-child { border-top: none; }
body.page-calc a.lp-list-item,
body.page-calc div.lp-list-item[onmousedown],
body.page-calc div.lp-list-item[onclick] { cursor: pointer; }
body.page-calc .lp-list-item:hover { background: rgba(255,255,255,0.05); }
body.page-calc .lp-chip-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  font-size: 10.5px;
  font-weight: 700;
  border-radius: 999px;
  background: rgba(var(--accent-rgb),0.16);
  color: var(--accent);
  border: none;
  transition: background 0.15s ease;
  white-space: nowrap;
}
body.page-calc .lp-chip-btn svg { width: 11px; height: 11px; stroke: currentColor; fill: none; flex-shrink: 0; }
body.page-calc .lp-chip-btn:hover { background: rgba(var(--accent-rgb),0.28); }
/* Click-to-copy affordance for item/job names (BOM rows, job cards) - a dotted underline plus an
   accent color on hover, so the interactivity reads at a glance without needing a dedicated icon
   on every single row. */
body.page-calc .copy-name { cursor: pointer; text-decoration: underline dotted rgba(255,255,255,0.25); text-underline-offset: 2px; transition: color 0.15s ease, text-decoration-color 0.15s ease; }
body.page-calc .copy-name:hover { color: var(--accent) !important; text-decoration-color: var(--accent); }
body.page-calc .lp-table { width: 100%; border-collapse: collapse; text-align: left; }
body.page-calc .lp-table th {
  padding: 8px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-mute);
  border-bottom: 1px solid rgba(255,255,255,0.08);
}
body.page-calc .lp-table th.sortable { cursor: pointer; user-select: none; }
body.page-calc .lp-table th.sortable:hover { color: var(--accent); }
body.page-calc .lp-table td { padding: 8px; border-bottom: 1px solid rgba(255,255,255,0.05); }
body.page-calc .lp-table tbody tr { transition: background 0.15s ease; }
body.page-calc .lp-table tbody tr:hover { background: rgba(255,255,255,0.03); }
/* Same "which row am I actually on" purpose as the table hover just above, for the expanded Job
   Materials list - a wide row (icon, name, qty/deficit, badge or Build button all on one line) is
   easy to lose track of vertically once the list has more than a couple of entries. */
body.page-calc .lp-mat-row:hover { background: rgba(255,255,255,0.05); }
body.page-calc .lp-table tr.lp-table-best { background: rgba(var(--accent-rgb),0.08); }
body.page-calc .lp-divider-col { padding-left: 24px; margin-left: 4px; border-left: 1px solid rgba(255,255,255,0.08); }
body.page-calc .lp-job-card {
  background: var(--glass-fill);
  border-radius: var(--radius-card);
  border-left: 3px solid transparent;
  transition: border-color 0.15s ease, background 0.2s ease;
}
/* Hover tint - a plain lighten, same value .lp-table rows already use for the same purpose, so a
   long/wide card (list mode's own horizontally-scrolling row, or a wide grid card) reads as "this one"
   at a glance without needing to track it against the ones above/below. Skips job-isolated, which
   already owns a much stronger always-on highlight (a hover tint on top would just muddy it) - and
   uses :not() rather than raising isolated's own specificity, so this stays a pure add-on that doesn't
   touch job-isolated's existing rule at all. */
body.page-calc .lp-job-card:not(.job-isolated):hover { background: rgba(255,255,255,0.045); }
body.page-calc .lp-job-card.job-ready { border-left-color: var(--accent); box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb),0.18), 0 0 20px rgba(var(--accent-rgb),0.10); }
body.page-calc .lp-job-card.job-started { border-left-color: rgba(var(--accent-rgb),0.45); }
body.page-calc .lp-job-card.job-subbuild { border-left-color: var(--text-mute); }
/* Visual hierarchy for a job + its prerequisites, replacing the "⚙ Prereq for: X" text line that
   used to be the only thing showing the relationship - a tree-style connector reads at a glance
   without adding a line of text to every child card. Works identically whether the queue is in grid
   or list view: .job-cluster is a plain flex column, so it behaves as one self-sizing block whether
   it's sitting in a CSS grid cell or stacked in a list - the grid/list wrapper never sees individual
   parent/child cards directly, only whole clusters, so nesting doesn't disturb either layout. */
body.page-calc .job-cluster { display: flex; flex-direction: column; gap: 8px; }
body.page-calc .job-cluster-children {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-left: 20px;
  padding-left: 14px;
  border-left: 2px solid rgba(255,255,255,0.1);
}
/* Isolation gets its own full-outline treatment (not just the left-edge color job-ready/job-started
   use) specifically so it reads as visually distinct from those - checking a job's isolation box is
   the one state that's about the card being SELECTED, not about its build status, and the checkbox
   itself has no visible label (see the always-on banner in the BOM panel for that), so this direct
   "you did something, here's the card that changed" feedback is what actually makes it legible. */
body.page-calc .lp-job-card.job-isolated { background: rgba(var(--accent-rgb),0.07); box-shadow: inset 0 0 0 1.5px var(--accent); }
body.page-calc .lp-group-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: var(--radius-btn);
  /* Was rgba(255,255,255,0.04) - almost identical to a job card's own --glass-fill wash
     (0.05 -> 0.013 gradient), so the "Pending" header (which unlike "In Progress" gets no accent
     tint) barely read as a separate element from the cards below it. Bumped for contrast and
     given its own bottom edge so the divide is unambiguous regardless of theme/monitor. */
  background: rgba(255,255,255,0.1);
  border-bottom: 1px solid var(--glass-border-top);
}
body.page-calc .lp-group-header.is-active { background: rgba(var(--accent-rgb),0.12); border-bottom-color: rgba(var(--accent-rgb),0.35); }
body.page-calc .lp-inset {
  background: rgba(0,0,0,0.18);
  border-radius: var(--radius-btn);
  padding: 8px 10px;
}
body.page-calc .lp-status-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 10px;
  border-radius: var(--radius-btn);
  background: rgba(255,255,255,0.04);
}
body.page-calc .lp-status-row.is-ready { background: rgba(var(--accent-rgb),0.14); }

/* --accent and --green are the same lime hex on this page (Foxstock treats one hero color as
   both brand accent and "up"/profit), which would otherwise make node-parent-highlight and
   node-child-highlight glow identically here - reintroducing the two-color distinction the base
   theme has (copper vs teal) using blue instead, paired with the matching connector-line color
   app.js draws for "output" connections. */
body.page-calc .diagram-node.node-parent-highlight {
  border-color: var(--blue) !important;
  box-shadow: var(--glass-shadow), 0 0 16px rgba(var(--blue-rgb),0.35) !important;
}

/* Header + icon rail carry no card chrome of their own - bare wordmark/icons sitting straight on
   the page canvas, matching Foxstock's minimal top nav rather than another floating panel. */
body.page-calc header,
body.page-calc .icon-rail {
  background: transparent !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  border: none !important;
  box-shadow: none !important;
}

/* Connection status stays a small flat pill, not a full gradient card - it's a one-line badge. */
body.page-calc #status-badge {
  background: rgba(255,255,255,0.05) !important;
  border: none !important;
  box-shadow: none !important;
}

/* Dark text only on the solid-lime fill variant - .btn-glass-muted keeps its own lime-on-tint
   color (set above), since this rule would otherwise win on specificity and force dark text onto
   a background that's mostly transparent, which would be unreadable. */
body.page-calc .btn-glass:not(.btn-glass-muted) { color: #0a1002; }

/* Stat strip: the strip itself is just a flex row now - each stat is its own gradient card
   (the Foxstock mini-card treatment) instead of one long bar cut up with divider lines. */
body.page-calc .stat-strip {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  gap: 12px;
}
body.page-calc .stat {
  background: var(--glass-fill);
  border: none;
  border-right: none;
  border-radius: var(--radius-card);
  box-shadow: var(--glass-shadow);
  padding: 14px 20px;
}

/* BOM sidebar, flyout panels, and the paste modal: drop the translucent-glass border for pure
   gradient fill - "treatment B" applied to every floating panel, not just the tree cards. Also
   kill backdrop-filter outright rather than leaving it at blur(var(--blur)) with --blur:0px -
   blur(0px) is still a non-"none" filter value, and per spec ANY non-none filter/backdrop-filter
   establishes a new containing block for position:fixed descendants. That silently turned
   .flyout-panel into the fixed-positioning origin for the search dropdown instead of the
   viewport, so JS-computed viewport coordinates landed wildly offset (rendered relative to the
   flyout's own box, not the page) - the dropdown then fell into the panel's own overflow-y:auto
   scroll area, which read as "the whole UI became a tiny scrollable page". */
body.page-calc #bom-sidebar,
body.page-calc .flyout-panel,
body.page-calc #paste-modal > div {
  border: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}
/* Same containing-block fix, without touching border (blueprint-browser-drawer keeps its real
   left-edge divider; .glass-card keeps its own dedicated border-width rule further down). */
body.page-calc #blueprint-browser-drawer,
body.page-calc .glass-card {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* Search/system/rig/market dropdown result lists: solid opaque fill instead of the ~25%-opacity
   black they had (the busy hero viewport behind them was showing straight through, making results
   hard to read), plus a higher z-index than .flyout-panel so they never render underneath one. */
body.page-calc [id$="-results"] {
  background: var(--glass-fill), var(--bg-0) !important;
  border-color: rgba(255,255,255,0.12) !important;
  border-radius: var(--radius-input);
  z-index: 500 !important;
}

/* Tree node cards keep a top-only border whose WIDTH lives here and whose COLOR is set inline
   per node type in app.js (blue = buy-mode, violet = reaction, accent = batch yield, transparent
   = default/root) - a colored cap instead of a full outline, so the category cue survives without
   reintroducing a bordered-card look. */
body.page-calc .glass-card {
  border-style: solid !important;
  border-width: 3px 0 0 0 !important;
  border-color: transparent !important;
  border-radius: var(--radius-card) !important;
}

/* Diagram viewport becomes the hero visualization instead of the flat tile + giant "E" watermark
   it had before: a near-black surface with a soft lime ambient glow rising from the bottom
   (echoes the Foxstock hero chart's backdrop) plus a faint dot-grid texture. This is the app's
   REAL tree diagram getting the hero treatment, not an invented chart - Foxstock's hero is a
   historical price line and this app has no genuine price history to plot honestly.
   Ledger's job queue and Invention's comparison table are each their own page's biggest/main
   content area, same role #viewport plays on the calculator - they get the identical treatment
   so the signature dot-grid + glow backdrop reads as "this site's look", not "one page's look". */
body.page-calc #viewport,
body.page-calc #journal-main-area,
body.page-calc #invention-main-area {
  background:
    radial-gradient(ellipse 1100px 620px at 50% 100%, rgba(var(--accent-rgb),.14), transparent 72%),
    radial-gradient(rgba(var(--accent-rgb),.07) 1px, transparent 1.5px) 0 0/20px 20px,
    var(--bg-0) !important;
  border: none !important;
  box-shadow: none !important;
}
body.page-calc #viewport::before { content: none; }

body.page-calc #viewport-hint {
  background: rgba(0,0,0,0.45) !important;
  border: none !important;
  box-shadow: none !important;
}

/* The root card's Net Profit figure - gradient-fill text so it reads as "the number that
   matters", same treatment Foxstock gives its balance card's revenue figure. */
body.page-calc .hero-num {
  font-size: 32px;
  font-weight: 800;
  letter-spacing: -0.01em;
  background: linear-gradient(135deg, #fff, #999);
  -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
  line-height: 1.15;
  display: inline-block;
}
body.page-calc .hero-num.profit { background: linear-gradient(135deg, #ddffa0, var(--accent)); -webkit-background-clip: text; background-clip: text; }
body.page-calc .hero-num.loss { background: linear-gradient(135deg, #f3c0a8, var(--red)); -webkit-background-clip: text; background-clip: text; }

/* LP Store Economics card (js/lpstore.js renderLPExtraStats) - a thin divider between each stat
   row instead of pure vertical gap, so the list reads as distinct fields rather than one loose
   stack of numbers. :first-child excluded via the adjacent-sibling selector, same trick used to
   space .fo-group above. */
.lp-econ-rows > div + div {
  border-top: 1px solid rgba(255,255,255,0.07);
  margin-top: 10px;
  padding-top: 10px;
}

/* ============================================================
   Market Economics pull-up drawer (LP Store isolation mode only - js/lpstore.js, lpstore.html)
   ============================================================ */
/* The pull tab: always visible at a fixed spot near the bottom of #viewport (which is
   position:relative), regardless of the drawer's own open/closed state - its own `bottom` offset
   moves instead (see .open below), so it always sits right at the drawer's current top edge like a
   real handle attached to the sheet, rather than getting buried under the open drawer's content. */
.lp-market-drawer-tab {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  z-index: 70;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px 7px 12px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  font-family: 'IBM Plex Mono', monospace;
  color: var(--text-soft);
  background: var(--glass-fill);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
  cursor: pointer;
  transition: bottom 0.25s ease, background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.lp-market-drawer-tab svg { width: 13px; height: 13px; stroke: currentColor; flex-shrink: 0; }
.lp-market-drawer-tab:hover { color: var(--accent); border-color: rgba(var(--accent-rgb),0.35); }
.lp-market-drawer-tab.open { bottom: 516px; color: var(--accent); }
.lp-market-drawer-tab.open.expanded { bottom: 736px; }
.lp-market-drawer-tab.open #lp-market-drawer-tab-chevron { transform: rotate(180deg); }
.lp-market-drawer-tab-chevron, #lp-market-drawer-tab-chevron { transition: transform 0.2s ease; }

/* The sheet itself: full width of the viewport, slides up from fully below it. opacity+visibility
   (not just the transform) so it's non-interactive and unreachable by keyboard/screen readers while
   closed, not merely scrolled out of sight. */
.lp-market-drawer {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  /* Sized to its actual content (stat tiles + both charts), not a guessed fixed px number - a
     fixed height was landing the volume chart below the fold on anything shorter than a tall
     desktop window, since #viewport's own available height varies with the browser/OS chrome
     around it. max-height is still a hard cap for genuinely short viewports, where content
     naturally becomes scrollable instead of overflowing. */
  height: auto;
  max-height: 84%;
  z-index: 65;
  display: flex;
  flex-direction: column;
  background: var(--glass-fill), var(--bg-0);
  border-top: 1px solid var(--glass-border);
  border-radius: var(--radius-panel) var(--radius-panel) 0 0;
  box-shadow: 0 -8px 24px rgba(0,0,0,0.35);
  transform: translateY(100%);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: transform 0.25s ease, opacity 0.2s ease;
}
/* Expanded state - toggled by the drawer header's own expand button. The charts themselves grow
   (see _lpMarketExpanded in js/lpstore.js), which combined with height:auto above is what actually
   makes the drawer taller - this just raises how much room they're allowed to grow into. */
.lp-market-drawer.expanded { max-height: 93%; }
.lp-market-drawer.open {
  /* none, not translateY(0) - an identity transform is still an ACTIVE transform, and any element
     with one gets promoted to its own GPU-composited layer for as long as that's true, not just
     during the slide-in transition. A composited layer gets rasterized/pasted onto the page as its
     own unit, which is a real, separate source of soft/blurred text and edges from anything actually
     wrong with this drawer's own SVG charts - every fix already made to their internal coordinates
     couldn't touch this, since it happens one level up, after the SVG has already rendered crisply.
     transform: none still animates correctly FROM translateY(100%) (browsers treat "no transform" as
     translateY(0) for interpolation purposes) - the drawer slides in exactly the same way - but once
     that transition ends and the drawer is sitting open and static, there's no active transform left
     to force the layer promotion, so the browser can render its content normally again. */
  transform: none;
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
.lp-market-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--glass-border);
  flex-shrink: 0;
}
.lp-market-drawer-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 16px 18px;
}

/* Stat strip - ONE thin bar, four label:value pairs separated by a hairline divider, not four
   separate boxy cards with icons and accent bars. That earlier version was flagged as too big/
   heavy for what's basically a quick readout - this is closer to a ticker line: compact, no card
   chrome per number, nothing competing with the charts below it for attention. */
.lp-market-stat-strip {
  display: flex;
  align-items: stretch;
  background: var(--glass-fill);
  border-radius: var(--radius-card);
  overflow: hidden;
}
.lp-market-stat-cell {
  flex: 1;
  min-width: 0;
  padding: 8px 14px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.lp-market-stat-cell + .lp-market-stat-cell { border-left: 1px solid var(--glass-border); }
.lp-market-stat-cell .stat-label { font-size: 9px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-market-stat-cell .stat-value { font-size: 14px; font-weight: 600; color: var(--text); }

/* Charts - deliberately plain: one flat-color line each (no gradients, no glow/blur, no bar
   chart), matching feedback that the earlier gradient/glow/bar treatment read as busy rather than
   polished. Text coordinates are rounded to whole pixels everywhere in js/lpstore.js (not
   sub-pixel .toFixed(1) values like the path geometry) - fractional positioning is what was making
   the axis labels look soft/blurry, a real SVG rendering quirk, not a color or size problem.

   THE ACTUAL "white outline on the candlesticks" / "text too bold and thick, blurry and glowing"
   bug, found via pixel-sampling then confirmed on the live, untouched DOM (getComputedStyle, no
   canvas/rasterizing tricks involved): svg[viewBox] { stroke: currentColor; } (search this file for
   that selector) is a sitewide reset for small line-art icon SVGs (nav icons, button icons - they
   want stroke:currentColor, fill:none). `stroke` is an INHERITED SVG property, and every chart SVG
   this file builds also carries a viewBox attribute, so that rule ALSO applied to every shape and
   every <text> glyph inside these charts that doesn't set its own stroke - candlestick rects, the
   price-area fill, and BOTH label classes below all silently got a 1px near-white
   (rgb(242,243,245), i.e. var(--text)) stroke on top of their real fill. That's a literal outline
   on the bars and a literal outline on every glyph - not blur, not color/size, an actual second
   paint operation, invisible in any of this file's own rules since none of them declared a stroke
   for these elements at all. The single stroke:none block below the grid rules resets it for both
   chart roots; grid lines/avg-line/crosshair/the price line's inline stroke/the hover-dot's own
   stroke all still work because a directly-set stroke always wins over an inherited one regardless
   of where in the tree it came from - only elements with no stroke of their own were affected. */
.lp-market-chart-grid line { stroke: rgba(255,255,255,0.06); stroke-width: 1; shape-rendering: crispEdges; }
.lp-market-chart-grid line.v { stroke: rgba(255,255,255,0.03); }
#lp-market-price-svg, #lp-market-volume-svg { stroke: none; }
.lp-market-chart-axis-label { font-size: 11px; fill: var(--text-soft); font-family: 'IBM Plex Mono', monospace; text-rendering: optimizeLegibility; }
.lp-market-avg-line { stroke: var(--text-mute); stroke-width: 1; stroke-dasharray: 3 3; shape-rendering: crispEdges; }
.lp-market-avg-label { font-size: 10px; fill: var(--text-mute); font-family: 'IBM Plex Mono', monospace; }
/* fill/stroke color is NOT set here on purpose - js/lpstore.js's dimmedMarketColor() computes a
   78%-toward-black mix of --accent/--red-400 (plain arithmetic, not color-mix()) and sets it as an
   inline style/attribute per element instead, since --accent alone reads too bright/saturated
   against this near-black card. See buildPriceLineChart/buildVolumeCandlestickChart. */
.lp-market-price-line { fill: none; stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }
.lp-market-price-area { opacity: 0.08; }
.lp-market-volume-line { fill: none; stroke: var(--text-soft); stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }
/* Flat fill, no stroke (the stroke:none reset above the grid rules is what actually guarantees
   that now - see its comment) - candlestick geometry is integer-rounded (see js/lpstore.js) and
   crispEdges is belt-and-suspenders on top of that: sharp axis-aligned rectangles, nothing worth
   anti-aliasing. Fill color itself is set inline per bar in js/lpstore.js.
   Hover swaps to a brightness bump (not an opacity toggle) so the highlighted bar stays a flat,
   fully-opaque color instead of turning translucent against the card background. */
.lp-market-candlestick { shape-rendering: crispEdges; }
.lp-market-candlestick.is-hovered { filter: brightness(1.3); }
.lp-market-hover-dot { stroke: var(--bg-0); stroke-width: 2; }
.lp-market-hover-dot.muted { fill: var(--text-soft); }
.lp-market-crosshair { stroke: rgba(255,255,255,0.2); stroke-width: 1; shape-rendering: crispEdges; }
.lp-market-chart-tooltip {
  position: absolute;
  pointer-events: none;
  background: var(--bg-0);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-sm);
  padding: 6px 9px;
  font-size: 11px;
  box-shadow: var(--glass-shadow);
  z-index: 10;
  white-space: nowrap;
}
.lp-market-chart-tooltip .lbl { color: var(--text-mute); font-size: 9.5px; }
.lp-market-chart-tooltip .val { color: var(--text); font-weight: 700; font-family: 'IBM Plex Mono', monospace; }

/* ============================================================
   Narrow-viewport fallback - .content-row (shared by all 3 pages) is a fixed-width side-by-side
   flex row (icon rail + diagram + BOM sidebar on index.html; two panes on ledger/invention). Below
   this it stacks vertically instead: nothing left permanently squeezed into a sliver, nothing
   clipped. Not a full mobile redesign - a floor under "actually usable" instead of "crushed".
   ============================================================ */
@media (max-width: 860px) {
  body.page-calc .content-row {
    flex-direction: column;
    overflow-y: auto;
    overflow-x: hidden;
    height: auto;
  }
  /* Every direct child (viewport, BOM sidebar, ledger's job list, invention's sidebar) stops
     fighting for a fixed slice of width and just takes the full row instead. */
  body.page-calc .content-row > * {
    width: 100% !important;
    max-width: 100% !important;
    flex: none !important;
  }
  body.page-calc #viewport {
    min-height: 60vh;
  }
  /* The icon rail switches from a tall narrow column to a horizontal strip along the top, so its
     icons stay reachable instead of stretching down the page as 80px-wide rows. */
  body.page-calc .icon-rail {
    width: 100%;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    padding: 10px 0;
  }
  /* .flyout-panel normally anchors 88px right of the (now full-width) rail at a fixed 340px wide -
     on a narrow screen that overflows the viewport sideways. Anchoring to the viewport instead and
     capping the width to what's actually available fixes that without changing how it opens/closes. */
  body.page-calc .flyout-panel {
    position: fixed !important;
    left: 12px !important;
    right: 12px !important;
    top: 72px !important;
    width: auto !important;
    max-width: none;
  }
}

/* ============================================================
   System & Structure flyout - redesigned. Replaces the old cramped 10px mono "SCI: / Stock: / EIV:"
   strip (which showed two useless counts and truncated the one number that matters) and the three
   bare underline-only rig inputs. Three pieces:
     .sys-readout   - a real system dossier card: name + security band, region, and the industry
                      cost indices with Manufacturing given the emphasis it deserves.
     .struct-chip   - the structure's ME/TE/cost bonuses parsed out as readable chips instead of
                      being crammed into the <option> text.
     .rig-slot      - numbered slot rows with a clear filled/empty state and a one-click clear.
   ============================================================ */
body.page-calc .sys-readout {
  background: rgba(0,0,0,0.22);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--radius-btn);
  padding: 8px 9px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
body.page-calc .sys-readout-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
body.page-calc .sys-readout-name {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text);
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.page-calc .sys-sec-pill {
  flex-shrink: 0;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: rgba(255,255,255,0.08);
  color: var(--text-soft);
  white-space: nowrap;
}
body.page-calc .sys-sec-pill.is-high { background: rgba(76,196,145,0.16); color: var(--green-300); }
body.page-calc .sys-sec-pill.is-low  { background: rgba(217,184,74,0.16); color: #e7cd77; }
body.page-calc .sys-sec-pill.is-null { background: rgba(221,107,100,0.16); color: var(--red-300); }
body.page-calc .sys-readout-region {
  font-size: 11px;
  color: var(--text-mute);
  margin-top: -4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.page-calc .sys-ci-primary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 9px;
  border-radius: var(--radius-sm);
  background: rgba(var(--accent-rgb),0.10);
}
body.page-calc .sys-ci-primary .sys-ci-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-soft);
}
body.page-calc .sys-ci-primary .sys-ci-value {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 17px;
  font-weight: 600;
  line-height: 1;
  color: var(--accent);
}
body.page-calc .sys-ci-secondary {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
body.page-calc .sys-ci-sub {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 5px 8px;
  border-radius: var(--radius-sm);
  background: rgba(255,255,255,0.035);
}
body.page-calc .sys-ci-sub .sys-ci-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-mute);
}
body.page-calc .sys-ci-sub .sys-ci-value {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-soft);
}
body.page-calc .sys-readout-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-top: 6px;
  border-top: 1px solid rgba(255,255,255,0.06);
  font-size: 10px;
  color: var(--text-mute);
}
body.page-calc .sys-eiv {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  font-weight: 700;
}
body.page-calc .sys-eiv-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-mute);
  flex-shrink: 0;
}
body.page-calc .sys-eiv.is-ready   .sys-eiv-dot { background: var(--green-400); box-shadow: 0 0 6px rgba(76,196,145,0.6); }
body.page-calc .sys-eiv.is-loading .sys-eiv-dot { background: #e7cd77; }
body.page-calc .sys-eiv.is-offline .sys-eiv-dot { background: var(--red-400); }
body.page-calc .sys-rigmult {
  font-family: 'IBM Plex Mono', monospace;
  letter-spacing: 0.02em;
}

body.page-calc .struct-bonus-row {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}
body.page-calc .struct-chip {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: rgba(255,255,255,0.05);
  color: var(--text-mute);
}
body.page-calc .struct-chip b {
  color: var(--text-soft);
  font-weight: 700;
}
body.page-calc .struct-chip.is-off { opacity: 0.45; }

body.page-calc .rig-slot-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
body.page-calc .rig-slot {
  display: flex;
  align-items: stretch;
  gap: 6px;
}
body.page-calc .rig-slot-num {
  flex-shrink: 0;
  width: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  background: rgba(255,255,255,0.05);
  color: var(--text-mute);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px;
  font-weight: 600;
}
body.page-calc .rig-slot.is-filled .rig-slot-num {
  background: rgba(var(--accent-rgb),0.18);
  color: var(--accent);
}
body.page-calc .rig-slot-main {
  flex: 1;
  min-width: 0;
  position: relative;
}
/* Shared bordered text input - rig slots, the system search, and the market search boxes all use
   this so every free-text field in the flyout looks the same (a real box at rest, not .field-line's
   invisible-until-focus underline). */
body.page-calc .rig-slot-input,
body.page-calc .fo-input {
  width: 100%;
  background: rgba(255,255,255,0.035) !important;
  border: 1px solid rgba(255,255,255,0.07) !important;
  border-radius: var(--radius-sm) !important;
  padding: 6px 9px !important;
  font-size: 11px;
  color: var(--text) !important;
  transition: border-color 0.15s ease, background 0.15s ease;
}
body.page-calc .rig-slot-input::placeholder,
body.page-calc .fo-input::placeholder { color: var(--text-mute); }
body.page-calc .rig-slot-input:hover,
body.page-calc .fo-input:hover { background: rgba(255,255,255,0.055) !important; }
body.page-calc .rig-slot-input:focus,
body.page-calc .fo-input:focus {
  outline: none;
  border-color: rgba(var(--accent-rgb),0.55) !important;
  background: rgba(255,255,255,0.06) !important;
}
body.page-calc .rig-slot.is-filled .rig-slot-input {
  border-color: rgba(var(--accent-rgb),0.28) !important;
  font-weight: 600;
}
body.page-calc .rig-slot-clear {
  flex-shrink: 0;
  width: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  background: rgba(255,255,255,0.04);
  color: var(--text-mute);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
body.page-calc .rig-slot-clear:hover { color: var(--red-400); background: rgba(221,107,100,0.14); }
body.page-calc .rig-slot:not(.is-filled) .rig-slot-clear { display: none; }

/* ============================================================
   Shared flyout-section vocabulary. Every section (Pricing / Build / Structure / Fees / Markets)
   is now built from the same two primitives the redesigned Structure section introduced, so moving
   between the icon-rail tabs shows one consistent language instead of five near-identical ones:
     .fo-card  - a titled sub-panel grouping one topic's controls.
     .fo-row   - a single labelled control line: label on the left, control(s) right-aligned.
   ============================================================ */
body.page-calc .fo-card {
  background: rgba(255,255,255,0.025);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--radius-btn);
  padding: 9px 10px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
body.page-calc .fo-card + .fo-card { margin-top: 8px; }
body.page-calc .fo-card-title {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-mute);
}
body.page-calc .fo-card-note {
  font-size: 10px;
  line-height: 1.45;
  color: var(--text-mute);
  margin-top: -2px;
}
body.page-calc .fo-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 26px;
}
body.page-calc .fo-row-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-soft);
  flex-shrink: 0;
}
body.page-calc .fo-row-value {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
body.page-calc .fo-row-ctl {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

/* Compact right-aligned numeric field (the tax / threshold %s). Its own box, not .field-line's
   invisible-until-focus underline - a settings value the user edits should look editable at rest. */
body.page-calc .fo-num {
  width: 4.2em;
  text-align: right;
  background: rgba(255,255,255,0.04) !important;
  border: 1px solid rgba(255,255,255,0.08) !important;
  border-radius: var(--radius-sm) !important;
  padding: 5px 7px !important;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  font-weight: 600;
  color: var(--text) !important;
  transition: border-color 0.15s ease, background 0.15s ease;
}
body.page-calc .fo-num:focus {
  outline: none;
  border-color: rgba(var(--accent-rgb),0.55) !important;
  background: rgba(255,255,255,0.06) !important;
}
body.page-calc .fo-num-suffix { font-size: 11px; color: var(--text-mute); }

/* Select that sits on the right of a .fo-row - sizes to its content instead of filling the row.
   The class rides onto js/custom-select.js's .csel-trigger too (it copies the <select>'s className),
   so both the hidden native control and the visible trigger pick this up. */
body.page-calc .fo-row select.fo-row-select,
body.page-calc .csel-trigger.fo-row-select {
  width: auto !important;
  min-width: 132px;
  max-width: 190px;
  font-size: 12px;
}

/* Small inline action (the optimizer "Optimize" buttons) - lighter than .btn-glass, reads as a
   secondary action attached to the field beside it. */
body.page-calc .fo-mini-btn {
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  background: rgba(var(--accent-rgb),0.14);
  color: var(--accent);
  cursor: pointer;
  transition: background 0.15s ease;
}
body.page-calc .fo-mini-btn:hover { background: rgba(var(--accent-rgb),0.24); }

/* Row of equal-width primary buttons (Build All / Buy All). */
body.page-calc .fo-btn-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
