/* C4 OPERATIONS — shared design tokens + primitives (2026-07-05, palette centralized 2026-07-07).
 * Loaded before ops_loop_editor.css + ops_overview.css. Both consumers read `var(--ops-*)`
 * from #ops_container's cascade so a single edit here restyles both surfaces.
 *
 * SITE RULE: NO hardcoded color literals anywhere (CSS or JS). Every color lives here as an
 * --ops-* token; consumers reference var(--ops-*) only. Translucent variants are derived with
 * color-mix() from a base token (never a fresh rgba). See .claude/CLAUDE.md (Styling section).
 *
 * STRUCTURAL RE-TINT: only the STRUCTURAL chrome tokens (dark panel / line / ring / arc
 * surfaces, grouped under the two "(hue-rotated)" sections below) are HSL, each with three
 * shared offsets added into its H / S / L via calc():
 *     --ops-hue  (deg)  rotate the chrome hue      (blue-gray → green-gray → purple-gray …)
 *     --ops-sat  (%)    +/- overall saturation     (punchier / flatter)
 *     --ops-lum  (%)    +/- overall lightness      (brighter / darker)
 * Set any of them on #ops_container to re-tint the whole UI chrome at once; default 0 = the
 * baseline hex colors noted in each token's comment. The CHROMATIC scheme (accent / semantic /
 * type / loop colors) stays FIXED as hex and ignores these knobs. Each token's own base H/S/L
 * are the first calc() terms — edit those to shift a single structural color.
 */

#ops_container,
#building_container,
.stkb_pick_bd,   /* + the buildings picker modal (mounts on <body>, so it carries the token scope directly) */
#dialog-c4-hab-place,   /* + the new-crafting-hab placement dialog (same #dialogContainer mount as below) */
#dialog-c4-place {   /* + the new-claim-stake placement dialog (mounts into the generic #dialogContainer,
                         outside #ops_container/#building_container — without this, .ops_btn's var(--ops-*)
                         colors resolve to nothing there and the "Place"/"Design" button renders as bare text). */
    /* ---- global HSL offsets (apply to the two "(hue-rotated)" sections only; each is ADDED
       to the token's base H/S/L, default 0 = no change) ---- */
    --ops-hue: 0deg;    /* + structural HUE        (deg; re-tint chrome) */
    --ops-sat: -17%;      /* + structural SATURATION (%;   +punchier / -flatter) */
    --ops-lum: -5%;      /* + structural LIGHTNESS  (%;   +brighter / -darker) */

    /* ---- neutrals / structure (hue-rotated) ---- */
    --ops-panel:      hsl(calc(219deg + var(--ops-hue)) calc(26% + var(--ops-sat)) calc(11% + var(--ops-lum)));  /* toolbars, side panes; #141922 */
    --ops-panel2:     hsl(calc(220deg + var(--ops-hue)) calc(28% + var(--ops-sat)) calc(15% + var(--ops-lum)));  /* inputs, chips; #1b2230 */
    --ops-panel2-hi:  hsl(calc(219deg + var(--ops-hue)) calc(27% + var(--ops-sat)) calc(19% + var(--ops-lum)));  /* panel2 hover; #232c3d */
    --ops-panel3:     hsl(calc(218deg + var(--ops-hue)) calc(32% + var(--ops-sat)) calc(12% + var(--ops-lum)));  /* popovers; #141b27 */
    --ops-card:       hsl(calc(220deg + var(--ops-hue)) calc(32% + var(--ops-sat)) calc(13% + var(--ops-lum)));  /* stop/craft cards; #161d2b */
    --ops-bg-deep:    hsl(calc(218deg + var(--ops-hue)) calc(29% + var(--ops-sat)) calc( 5% + var(--ops-lum)));  /* deepest sunken; #0a0d12 */
    --ops-bg-sunken:  hsl(calc(220deg + var(--ops-hue)) calc(37% + var(--ops-sat)) calc( 9% + var(--ops-lum)));  /* sunken inputs; #0f1521 */
    --ops-panel-dark: hsl(calc(220deg + var(--ops-hue)) calc( 6% + var(--ops-sat)) calc( 7% + var(--ops-lum)));  /* overview strip; ~#111111 (tiny sat so it tints) */
    --ops-grid-dot:   hsl(calc(215deg + var(--ops-hue)) calc(33% + var(--ops-sat)) calc(13% + var(--ops-lum)));  /* stage dot grid; #161f2c */
    --ops-line:       hsl(calc(219deg + var(--ops-hue)) calc(24% + var(--ops-sat)) calc(28% + var(--ops-lum)));  /* default borders; #2a3344 */
    --ops-line-soft:  hsl(calc(218deg + var(--ops-hue)) calc(28% + var(--ops-sat)) calc(22% + var(--ops-lum)));  /* inner dividers; #1d2533 */
    --ops-line-dark:  hsl(calc(220deg + var(--ops-hue)) calc( 6% + var(--ops-sat)) calc(10% + var(--ops-lum)));  /* overview borders; ~#1a1a1a (tiny sat so it tints) */

    /* ---- LOOP GRAPHICS (nodes + arcs of the loop editor / schema) ----
       Separate from the surrounding UI chrome above: its own --ops-lg-hue/sat/lum offset knobs
       so the loop diagram can be re-tinted INDEPENDENTLY of the panels/toolbars. Each token's
       first calc() term is its own base H/S/L (baseline hex noted in the comment). */
    --ops-lg-hue: 0deg;   /* + loop-graphics HUE        */
    --ops-lg-sat: -17%;     /* + loop-graphics SATURATION */
    --ops-lg-lum: 0%;     /* + loop-graphics LIGHTNESS  */
    --ops-node-head: hsl(calc(220deg + var(--ops-lg-hue)) calc(28% + var(--ops-lg-sat)) calc(16% + var(--ops-lg-lum)));  /* node headline bg; ~#1b2333 */
    --ops-node-bg:   hsl(calc(220deg + var(--ops-lg-hue)) calc(32% + var(--ops-lg-sat)) calc(13% + var(--ops-lg-lum)));  /* node body bg; #161d2b */
    --ops-node-line: hsl(calc(219deg + var(--ops-lg-hue)) calc(24% + var(--ops-lg-sat)) calc(22% + var(--ops-lg-lum)));  /* node borders; #2a3344 */
    --ops-arc:       hsl(calc(219deg + var(--ops-lg-hue)) calc(27% + var(--ops-lg-sat)) calc(33% + var(--ops-lg-lum)));  /* per-leg arc line; #3d4d6b */
    --ops-ring:      hsl(calc(220deg + var(--ops-lg-hue)) calc(28% + var(--ops-lg-sat)) calc(28% + var(--ops-lg-lum)));  /* loop backdrop ring; #33405a */
    --ops-muted:     hsl(calc(219deg + var(--ops-lg-hue)) calc(24% + var(--ops-lg-sat)) calc(38% + var(--ops-lg-lum)));  /* unplaced node borders, unset movement; #4a5a78 */

    /* ---- neutrals / structure (hex, not hue-rotated) ---- */
    --ops-bg: transparent;        /* map/canvas shows through */
    --ops-txt: #cdd6e4;           /* body text */
    --ops-txt-hi: #fbfbfb;        /* high-contrast text (active tabs, btn hover) */
    --ops-btn-txt: #bbbbbb;       /* overview button text */
    --ops-tab-txt: #aaaaaa;       /* overview tab idle text */
    --ops-dim: #7f8da3;           /* dim/secondary text */
    --ops-dim2: #5a6577;          /* dimmest text */
    --ops-ink: #000000;           /* text on bright accent */

    /* ---- chromatic scheme (fixed hex, NOT hue-rotated) ---- */
    --ops-accent:  #d0ff00;
    --ops-accent2: #e8ff4d;             /* accent hover */
    --ops-red:     var(--col-stopped);
    --ops-green:   var(--col-green);
    --ops-launch:  var(--col-green);      /* launch button */
    --ops-info:    var(--col-blue);       /* info  */
    --ops-warn:    var(--col-warning);    /* warn */
    --ops-orange:  var(--col-orange);     /* mid urgency, between warn and red — NOTE: --col-orange currently
                                              shares dark-ui.css's exact hex with --col-warning (#ffd74d), so
                                              this won't read as visually distinct from --ops-warn until that's
                                              addressed site-wide; using the correct token now regardless. */

    /* Stop / entity type palette — mirrors PT in ops_loop_editor_c4.js so the same colors show
       on radial stops AND overview entity nodes. */
    --ops-type-base:    #4ea1ff;
    --ops-type-base-lt: #8fb4ff;  /* movement time */
    --ops-type-asteroid:#ffc24b;
    --ops-type-point:   #c08bff;
    --ops-type-cs:      var(--col-green);

    /* Loop palette — LOOP_COLORS in ops_schema_view_c4.js / ops_overview_c4.js / ops_map_paths_c4.js
       read these (via getComputedStyle for SVG/canvas, var() for inline styles). */
    --ops-loop-1: #7cd0ff;
    --ops-loop-2: #ff9db3;
    --ops-loop-3: #ffcf8a;
    --ops-loop-4: #b9a0ff;
    --ops-loop-5: #8affc7;
    --ops-loop-6: #ffb0e0;
    --ops-loop-7: #c0c8ff;
    --ops-loop-8: #ffe27a;
    /* Claim-stake building CATEGORY palette (buildings editor) — alias the loop/type colors so category
       identity has ONE source; unify later by re-pointing here. */
    --ops-cat-hub:        var(--ops-type-cs);
    --ops-cat-extraction: var(--ops-loop-5);
    --ops-cat-processing: var(--ops-loop-1);
    --ops-cat-power:      var(--ops-loop-3);
    --ops-cat-storage:    var(--ops-loop-7);
    --ops-cat-crew:       var(--ops-loop-2);
    --ops-cat-farm:       var(--ops-loop-8);
    /* Thin outline stroked around the loop-path direction arrows on the map so the colored triangle
       doesn't melt into the routes beneath it (ops_map_paths_c4.js resolves this via getComputedStyle). */
    --ops-map-stroke: #000;

    /* ---- overlay + shadow bases (for color-mix tints below) ---- */
    --ops-overlay: #ffffff;       /* neutral white hover base */
    --ops-shadow:  #000000;       /* drop-shadow base */

    /* ---- derived tints (color-mix off a base token) ---- */
    --ops-accent-08:  color-mix(in srgb, var(--ops-accent) 8%,  transparent);
    --ops-accent-12:  color-mix(in srgb, var(--ops-accent) 12%, transparent);
    --ops-red-12:     color-mix(in srgb, var(--ops-red) 12%,    transparent);
    --ops-green-12:   color-mix(in srgb, var(--ops-green) 12%,  transparent);
    --ops-launch-12:  color-mix(in srgb, var(--ops-launch) 12%, transparent);
    --ops-launch-14:  color-mix(in srgb, var(--ops-launch) 14%, transparent);
    --ops-warn-20:    color-mix(in srgb, var(--ops-warn) 20%,   transparent);
    --ops-warn-pale:  color-mix(in srgb, var(--ops-warn) 62%, var(--ops-overlay)); /* pale yellow — partly-blocked craft tab dot */
    --ops-info-14:    color-mix(in srgb, var(--ops-info) 14%,   transparent);
    --ops-sel-08:     color-mix(in srgb, var(--ops-green) 8%,   transparent);
    --ops-sel-12:     color-mix(in srgb, var(--ops-green) 12%,  transparent);
    /* dark tinted hover backgrounds (menu locked item / amber cfg border) */
    --ops-red-dim-bg:      color-mix(in srgb, var(--ops-red) 20%, var(--ops-card));
    --ops-asteroid-border: color-mix(in srgb, var(--ops-type-asteroid) 25%, var(--ops-bg-deep));
    /* neutral overlays */
    --ops-overlay-02: color-mix(in srgb, var(--ops-overlay) 2%,  transparent);
    --ops-overlay-03: color-mix(in srgb, var(--ops-overlay) 3%,  transparent);
    --ops-overlay-04: color-mix(in srgb, var(--ops-overlay) 4%,  transparent);
    --ops-overlay-05: color-mix(in srgb, var(--ops-overlay) 5%,  transparent);
    --ops-overlay-06: color-mix(in srgb, var(--ops-overlay) 6%,  transparent);
    --ops-overlay-10: color-mix(in srgb, var(--ops-overlay) 10%, transparent);
    --ops-overlay-15: color-mix(in srgb, var(--ops-overlay) 15%, transparent);
    --ops-overlay-30: color-mix(in srgb, var(--ops-overlay) 30%, transparent);
    /* shadows */
    --ops-shadow-45: color-mix(in srgb, var(--ops-shadow) 45%, transparent);
    --ops-shadow-55: color-mix(in srgb, var(--ops-shadow) 55%, transparent);
    --ops-shadow-60: color-mix(in srgb, var(--ops-shadow) 60%, transparent);
}

/* ============================================================================
   SHARED OPERATIONS COMPONENTS  (2026-07-15 — UI unification pass)
   ----------------------------------------------------------------------------
   ONE class vocabulary for every Operations view/editor: header bars, section
   titles, tab strips, activity dots, action buttons. Replaces the per-view
   .ov_/.ofl_/.le_/.ce_ header classes (those files are being folded in + deleted).
   Scoped to #ops_container so the --ops-* tokens resolve. NO hardcoded literals
   (site rule) — every color is a token or a color-mix off one.
   ============================================================================ */

/* Context-header host (#ops_overview) — the always-visible FLEET LOOPS + CRAFTING JOBS rows.
   flex-direction:column so the two .ops_bar rows STACK (not sit side-by-side). Content-height
   only; the active view host below fills the rest. (Folded from the retired ops_overview.css.) */
#ops_overview {
    display: none;              /* opsLoopMode flips to flex when the overlay is shown */
    flex-direction: column;
    flex: 0 0 auto;
    background: transparent;    /* map underneath, no opaque bg */
}
#ops_overview * { box-sizing: border-box; }

/* A header row. Rows 1/2 = the shared FLEET LOOPS / CRAFTING JOBS context bars;
   row 3 = each view's own header. Matches the loop editor's .le_bar (the canonical
   chrome): --ops-panel bg, 7px 12px padding, 8px gap, bottom line. */
.ops_bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    border-bottom: 1px solid var(--ops-line);
    background: var(--ops-panel);
    flex: 0 0 auto;
}
.ops_bar * { box-sizing: border-box; }

/* Section title — "FLEET LOOPS" / "CRAFTING JOBS" / "MATERIAL FLOWS". min-width so the
   two stacked context bars' tab strips line up, while longer titles still fit. */
.ops_title {
    flex: 0 0 auto;
    min-width: 116px;
    font: 600 12px EEMfont, Helvetica, Arial, sans-serif;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ops-txt-hi);
    white-space: nowrap;
}

/* Tab strip — flex-fills the middle, wraps, pushes trailing buttons to the right. */
.ops_tabrow {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

/* One tab (a fleet loop or a crafting starbase). Loop/accent underline via --tab-color. */
.ops_tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 2px;
    padding: 4px 10px;
    cursor: pointer;
    font: 400 11px EEMfont, Helvetica, Arial, sans-serif;
    color: var(--ops-tab-txt);
    border-bottom: 2px solid transparent;   /* loop/accent underline shows on HOVER only (user pref) */
    transition: color .12s ease, background-color .12s ease, border-color .12s ease;
}
.ops_tab:hover {
    color: var(--ops-txt-hi);
    background: var(--ops-overlay-06);
    border-bottom-color: var(--tab-color, var(--ops-dim));
}
.ops_tab_name { color: var(--ops-txt-hi); font-weight: 400; white-space: nowrap; }
.ops_tab_sub  { color: var(--ops-dim); font-size: 11px; white-space: nowrap; }
.ops_tab_sub_fleet { color: var(--col-fleet); }
.ops_tab_sub_none  { font-style: italic; }
.ops_tab_empty { padding: 4px 8px; color: var(--ops-dim); font-size: 11px; font-style: italic; cursor: default; }
.ops_tab_empty b { color: var(--ops-accent); font-style: normal; }

/* Activity dot — running (green, pulsing) / paused (blue) / stopped (red). */
.ops_dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.ops_dot_green { background: var(--ops-green); box-shadow: 0 0 6px var(--ops-green); animation: ops_run_pulse 1.6s ease-in-out infinite; }
.ops_dot_red   { background: var(--ops-red);   box-shadow: 0 0 6px var(--ops-red); }
.ops_dot_blue  { background: var(--ops-info);  box-shadow: 0 0 6px var(--ops-info); }
/* partly-blocked crafting starbase tab (some jobs running, some blocked) → pale-yellow blink. */
.ops_dot_pblock { background: var(--ops-warn-pale); box-shadow: 0 0 6px var(--ops-warn); animation: ops_dot_blink 1s steps(1, end) infinite; }
/* active-but-blocked crafting job/starbase → red blink (needs attention, distinct from steady idle-red). */
.ops_dot_blockflash { background: var(--ops-red); box-shadow: 0 0 6px var(--ops-red); animation: ops_dot_blink 1s steps(1, end) infinite; }
/* engine-parked (HELD) — steady amber; needs a human, distinct from user-paused blue and hard-block red. */
.ops_dot_held { background: var(--ops-warn); box-shadow: 0 0 6px var(--ops-warn); }
@keyframes ops_run_pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.75); } }
@keyframes ops_dot_blink { 0% { opacity: 1; } 50% { opacity: .2; } }

/* Canonical action button — NEW + editor actions. Mirrors the loop editor's
   `#ops_loop_editor button` (bg panel2, line border, 4px radius, hover → accent border+text).
   Semantic modifiers (launch/stop) tint like .le_launch / .le_btn_stop. */
.ops_btn {
    flex: 0 0 auto;
    display: inline-block;
    background: var(--ops-panel2);
    color: var(--ops-txt);
    border: 1px solid var(--ops-line);
    border-radius: 4px;
    padding: 4px 10px;
    font: 500 11px EEMfont, Helvetica, Arial, sans-serif;
    line-height: 1.4;
    cursor: pointer;
    white-space: nowrap;
    transition: color .12s ease, border-color .12s ease, background-color .12s ease;
}
.ops_btn:hover { border-color: var(--ops-accent); color: var(--ops-accent); }
.ops_btn_launch { border-color: var(--ops-launch); color: var(--ops-launch); }
.ops_btn_launch:hover { color: var(--ops-launch); background: var(--ops-launch-12); }
.ops_btn_stop { border-color: var(--ops-red); color: var(--ops-red); }
.ops_btn_stop:hover { color: var(--ops-red); background: var(--ops-red-12); }
.ops_btn.dim { opacity: .45; pointer-events: none; }

.ops_dim { color: var(--ops-dim); }

/* Canonical circular "+" add button — dashed idle, solid accent on hover. Appearance lifted from the loop
   editor's #ops_loop_editor .le_plus (which can migrate onto this in a verified pass; its position:absolute
   ring placement stays local). Positioning is the consumer's job. */
.ops_plus {
    width: 30px; height: 30px; border-radius: 50%; flex: 0 0 auto; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ops-panel2); border: 1.5px dashed var(--ops-muted);
    color: var(--ops-dim); font-size: 18px; line-height: 1;
    transition: color .12s, border-color .12s;
}
.ops_plus:hover { border-color: var(--ops-accent); color: var(--ops-accent); border-style: solid; }

/* Canonical small UPPERCASE label — section sub-headers, column heads, field labels, node "type" tags.
   ONE source for label typography (was ~17 near-duplicate 9-11px uppercase rules across the loop editor /
   flows / config panes — migrate those onto .ops_label per-surface as each is touched). Size hierarchy via
   .ops_label_md (10px) / .ops_label_lg (11px); spacing/weight/transform/color stay constant. */
.ops_label    { font-size: 9px;  font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ops-dim); }
.ops_label_md { font-size: 10px; }
.ops_label_lg { font-size: 11px; }

/* Canonical labelled meter/gauge — a label+value row over a track+fill bar. Used by capacity bars, the
   claim-stake gauges, and reusable by any "N / cap" readout. Fill width + color are set inline (JS). The
   label uses .ops_label. */
.ops_meter       { display: flex; flex-direction: column; }
.ops_meter_top   { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.ops_meter_val   { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ops-txt-hi); }
.ops_meter_track { height: 5px; margin-top: 6px; border-radius: 3px; background: var(--ops-bg-deep); overflow: hidden; }
.ops_meter_fill  { height: 100%; border-radius: 3px; }

/* Canonical numeric value — tabular figures + semantic +/- tint. */
.ops_num      { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ops-txt-hi); }
.ops_num.pos  { color: var(--ops-green); }
.ops_num.neg  { color: var(--ops-red); }
.ops_num.warn { color: var(--ops-warn); }

/* Centered "nothing here yet / pick something" placeholder that fills a view body. */
.ops_placeholder {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px;
    text-align: center;
    color: var(--ops-dim);
    font-size: 14px;
}
.ops_placeholder b { color: var(--ops-accent); font-weight: 600; }

/* Shared view-host base — every switchable Operations view (schema / flows / loop editor / crafting)
   fills the area below the context header. display:none is the default; opsLoopMode / opsSetViewMode
   flip it to flex inline when selected. Replaces the per-host #ops_* base rules (no id scoping). */
.ops_view { display: none; flex: 1 1 0; min-height: 0; overflow: hidden; }

/* ---- Shared container "node" — ONE box style for loop-editor stops AND crafting starbase windows.
   Same element = same class (see feedback_shared_classes_not_parallel_styles). Class-based, NO id
   scoping — the --ops-* tokens inherit from the #ops_container ancestor. View-specific LAYOUT
   (position / width) stays on .le_stop / .ce_sb; these own the CHROME + typography only. ---- */
.ops_node { background: var(--ops-node-bg); border: 1px solid var(--ops-node-line); border-radius: 9px; overflow: hidden; }

.ops_node_bevel { 
    background: var(--ops-node-bg); 
    border: 1px solid var(--ops-node-line); 
    border-radius: 28px / 4px; 
    corner-shape: bevel; 
    overflow: hidden; 
}

.ops_node_head {
    display: flex; align-items: center; gap: 5px;
    padding: 5px 8px; font-size: 11px;
    background: var(--ops-node-head); border-bottom: 1px solid var(--ops-node-line);
}
.ops_node_dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.ops_node_type { font-size: 9px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ops-dim); } /* == .ops_label */
.ops_node_title { font-weight: 600; letter-spacing: .5px; color: var(--ops-type-base); }
.ops_node_title_info { font-family: EEMfontLight; letter-spacing: .5px; color: var(--ops-type-base); opacity: 0.6; font-style: italic }
.ops_node_meta { margin-left: auto; color: var(--ops-dim); font-size: 10px; }
.ops_node_body { padding: 4px 7px; font-size: 11px; color: var(--ops-dim); }
.ops_node_body .ops_node_name { color: var(--ops-txt); }
.ops_node_row {
    display: flex; align-items: center; gap: 6px;
    padding: 5px 8px; border-bottom: 1px solid var(--ops-node-line); font-size: 11px;
}
.ops_node_row:last-child { border-bottom: none; }
/* row cells — a name cell + qty/meta cell + delete-× — shared by crafting job rows AND the loop
   editor's crafting-container rows (.jrow .rn/.qc/.x). */
.ops_row_name { flex: 1 1 auto; min-width: 0; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ops-txt); }
.ops_row_qty { color: var(--ops-dim); font-size: 10px; white-space: nowrap; }
.ops_row_del { color: var(--ops-dim); cursor: pointer; padding: 0 2px; font-size: 11px; }
.ops_row_del:hover { color: var(--ops-red); }
.ops_node_flows { border-top: 1px solid var(--ops-node-line); padding: 4px 8px; }
.ops_node_flows_hd { font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--ops-dim); margin-bottom: 2px; } /* == .ops_label + margin */


/* ==== simple mining-loop list (ops_loop_list_c4.js) — v1-style inline rows, ops-native chrome ==== */
#ops_simple_list { flex-direction: column; }
.ml_root { flex: 1 1 0; min-height: 0; overflow: auto; padding: 10px 12px; box-sizing: border-box; }
.ml_section { margin: 0; }
.ml_section > .ops_node_head { position: sticky; top: 0; z-index: 2; }
.ml_add { margin-left: auto; }
/* Collapsible list sections: caret + count in the header; body hidden when .ml_collapsed (everything
   in the section except its sticky header). Toggle is only the .ml_sectoggle cluster so header buttons
   keep working. */
.ml_sectoggle { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; user-select: none; }
.ml_caret { display: inline-block; width: 0.8em; font-size: 0.8em; opacity: 0.7; transition: opacity 0.12s; }
.ml_sectoggle:hover .ml_caret { opacity: 1; }
.ml_count { font-size: 0.85em; opacity: 0.65; }
/* Section headers (list view): light-grey title (was accent), larger caret, readable count + issue badge. */
.ml_sectoggle .ops_node_title { color: var(--ops-txt); }
.ml_sectoggle .ml_caret { font-size: 14px; width: auto; opacity: 0.85; color: var(--ops-txt); }
.ml_sectoggle:hover .ml_caret { opacity: 1; }
.ml_sectoggle .ml_count { color: var(--ops-txt); opacity: 0.85; font-size: 0.82em; }
.ml_sectoggle .ml_count_issue { color: var(--ops-red); margin-left: 5px; font-weight: 600; }
.ml_section.ml_collapsed > *:not(.ops_node_head) { display: none; }
/* header + rows share one grid so columns line up: dot | name | fleet | base | asteroid | resources | travel | runs | actions */
.ml_head, .ml_row {
    display: grid;
    grid-template-columns: 16px minmax(90px, 1.1fr) 0.9fr 1.3fr 1.2fr 1.5fr 1fr 100px 148px;
    align-items: center;
    gap: 8px;
}
.ml_head {
    padding: 5px 8px;
    font-size: 9px; text-transform: uppercase; letter-spacing: .5px; color: var(--ops-dim);
    border-bottom: 1px solid var(--ops-node-line);
    background: var(--ops-node-head);
}
.ml_row { padding: 5px 8px; border-bottom: 1px solid var(--ops-node-line); font-size: 11px; }
.ml_row:last-child { border-bottom: none; }
.ml_row:hover { background: color-mix(in srgb, var(--ops-overlay) 4%, transparent); }
/* loop name — reads as a bold label, reveals its input chrome on hover/focus (inline rename) */
#ops_simple_list input.ml_name_inp {
    width: 100%; min-width: 0;
    background: transparent; color: var(--ops-type-base); font-weight: 600;
    border: 1px solid transparent; border-radius: 4px; padding: 3px 5px;
    font: 600 11px EEMfont, Helvetica, Arial, sans-serif;
    text-overflow: ellipsis;
}
#ops_simple_list input.ml_name_inp:hover { border-color: var(--ops-line); }
#ops_simple_list input.ml_name_inp:focus { outline: none; background: var(--ops-bg-sunken); border-color: var(--ops-accent); color: var(--ops-txt-hi); }
.ml_v2 { color: var(--ops-dim); }
.ml_v2:hover { color: var(--ops-accent); }
.ml_c { min-width: 0; }
.ml_c_btns { display: flex; gap: 4px; justify-content: flex-end; }
.ml_h_act { text-align: right; }
.ml_row .ops_btn { padding: 3px 7px; }
.ml_start.ml_dis { opacity: .4; }
/* Uniform SQUARE action buttons in every list row (start/stop · dupe · del · ↗v2 · ∞). Variable-width
   glyphs rendered each button 30-33px wide → inconsistent AND 4 of them overflowed the 116px ACTIONS
   column, spilling left into the TRAVEL select. Fixed 24×24 squares line up across sections + fit. */
#ops_simple_list .ml_c_btns .ops_btn,
#ops_simple_list .ml_inf,
#ops_simple_list .ml_loop_inf,
#ops_simple_list .ml_edit_begin,
#ops_simple_list .ml_edit_save,
#ops_simple_list .ml_edit_cancel {
    box-sizing: border-box;
    width: 24px; height: 24px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 12px; line-height: 1;
}
/* Same 24×24 box — but the stop's red border + ∞'s bright accent border + bold ∞ glyph made those two
   READ as bigger than the dim-bordered del/↗. Show state by ICON color only (red ■, accent ∞), dim the
   borders to match the rest, and drop the bold — so all four look uniform. */
#ops_simple_list .ml_inf, #ops_simple_list .ml_loop_inf { font-weight: 500; }
#ops_simple_list .ml_c_btns .ops_btn_stop,
#ops_simple_list .ml_inf_on { border-color: var(--ops-line); }
/* inline selects — match the sunken-input look used elsewhere in the ops surfaces */
#ops_simple_list select.ml_sel {
    width: 100%; max-width: 100%;
    background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 4px;
    padding: 3px 4px; font: 500 11px EEMfont, Helvetica, Arial, sans-serif; cursor: pointer;
}
#ops_simple_list select.ml_sel:disabled { opacity: .45; cursor: not-allowed; }
#ops_simple_list select.ml_sel:focus { outline: none; border-color: var(--ops-accent); }
/* searchable base/asteroid combo — input in the row, floating filtered list (.ml_combopop) under it */
#ops_simple_list input.ml_combo_inp {
    width: 100%; max-width: 100%; box-sizing: border-box;
    background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 4px;
    padding: 3px 6px; font: 500 11px EEMfont, Helvetica, Arial, sans-serif; cursor: text; text-overflow: ellipsis;
}
#ops_simple_list input.ml_combo_inp:disabled { opacity: .45; cursor: not-allowed; }
#ops_simple_list input.ml_combo_inp:focus { outline: none; border-color: var(--ops-accent); }
.ml_combopop {
    position: fixed; z-index: 1000000; max-height: 280px; overflow-y: auto; box-sizing: border-box;
    background: var(--ops-bg-sunken); border: 1px solid var(--ops-line); border-radius: 4px;
    box-shadow: 0 6px 18px color-mix(in srgb, var(--ops-shadow) 55%, transparent); font-size: 11px;
}
.ml_combo_opt { padding: 4px 8px; cursor: pointer; color: var(--ops-txt); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml_combo_opt:hover { background: color-mix(in srgb, var(--ops-overlay) 6%, transparent); color: var(--ops-accent); }
.ml_combo_empty, .ml_combo_more { padding: 5px 8px; font-size: 10px; color: var(--ops-dim); font-style: italic; }
.ml_combo_more { border-top: 1px solid var(--ops-line); }
/* resource picker field (looks like a select, opens a checklist popup) */
.ml_respick {
    display: flex; align-items: center; gap: 4px; width: 100%; min-width: 0; box-sizing: border-box;
    background: var(--ops-bg-sunken); border: 1px solid var(--ops-line); border-radius: 4px;
    padding: 3px 6px; font-size: 11px; color: var(--ops-txt); cursor: pointer;
}
.ml_respick:hover { border-color: var(--ops-accent); }
.ml_respick.ml_disabled { opacity: .45; pointer-events: none; }
.ml_reschips { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml_caret { flex: 0 0 auto; color: var(--ops-dim); font-size: 9px; }
.ml_dim { color: var(--ops-dim); }
.ml_empty { padding: 20px; text-align: center; color: var(--ops-dim); font-size: 13px; line-height: 1.5; }
/*.ml_empty .accent { color: var(--ops-accent); }*/
/* resource checklist popup (appended under #ops_container so --ops-* tokens resolve) */
.ml_respop {
    position: fixed; z-index: 1000000; min-width: 220px; max-width: 300px;
    background: var(--ops-card); border: 1px solid var(--ops-line); border-radius: 8px;
    box-shadow: 0 6px 24px color-mix(in srgb, var(--ops-shadow) 55%, transparent);
    font-size: 11px; color: var(--ops-txt); overflow: hidden;
}
.ml_resh { padding: 6px 10px; background: var(--ops-node-head); border-bottom: 1px solid var(--ops-line); font-weight: 600; }
/* Live "selected: N" beside the picker's cap. Normal weight so it reads as a running readout rather
   than part of the title; turns accent AT the cap so "why can't I tick another?" answers itself. */
.ml_rescount { font-weight: 400; font-variant-numeric: tabular-nums; }
.ml_rescount_cap { color: var(--ops-accent); }
.ml_reslist { max-height: 260px; overflow: auto; padding: 4px 0; }
.ml_resrow { display: flex; align-items: center; gap: 6px; padding: 3px 10px; cursor: pointer; }
.ml_resrow:hover { background: color-mix(in srgb, var(--ops-overlay) 5%, transparent); }
.ml_resnm { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml_resrich { flex: 0 0 auto; color: var(--ops-type-asteroid); font-size: 10px; }
.ml_resf { padding: 6px 10px; border-top: 1px solid var(--ops-line); text-align: right; }

/* Advanced section — read-mostly rows (rename + run only): dot | name | fleet(ro) | route(ro) | runs | actions.
   Own grid; declared AFTER .ml_head/.ml_row so it wins on the shared .ml_head element. */
/* Advanced renders FIRST in the list (render() in ops_loop_list_c4.js), so the separating gap the other
   sections carry above them would be a stray indent at the very top — :first-child drops it. It needs the
   gap on its BOTTOM instead: every other section spaces itself with margin-top, but Mining (which follows
   Advanced) has none, being the normal first section. */
.ml_section_adv { margin-top: 10px; margin-bottom: 10px; }
.ml_section_adv:first-child { margin-top: 0; }
.ml_head_adv, .ml_row_adv {
    display: grid;
    grid-template-columns: 16px minmax(90px, 1.2fr) 0.9fr 2fr 100px 148px;
    align-items: center; gap: 8px;
}
.ml_row_adv { padding: 5px 8px; border-bottom: 1px solid var(--ops-node-line); font-size: 11px; }
.ml_row_adv:last-child { border-bottom: none; }
.ml_row_adv:hover { background: color-mix(in srgb, var(--ops-overlay) 4%, transparent); }
.ml_adv_fleet { color: var(--ops-txt); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml_adv_sum { color: var(--ops-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Scanning section — dot | name | fleet | base | scan-sector(X/Y + map + fleet-loc) | pattern | travel | runs | actions */
.ml_section_scan { margin-top: 10px; }
.ml_head_scan, .ml_row_scan {
    display: grid;
    grid-template-columns: 16px minmax(84px, 1fr) 0.85fr 1.1fr 1.4fr 1.2fr 60px 0.9fr 100px 148px;
    align-items: center; gap: 8px;
}
.ml_row_scan { padding: 5px 8px; border-bottom: 1px solid var(--ops-node-line); font-size: 11px; }
.ml_row_scan:last-child { border-bottom: none; }
.ml_row_scan:hover { background: color-mix(in srgb, var(--ops-overlay) 4%, transparent); }
.ml_scan_target { display: flex; align-items: center; gap: 3px; min-width: 0; }
#ops_simple_list input.ml_xy {
    width: 44px; min-width: 0; box-sizing: border-box;
    background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 4px; padding: 3px 4px;
    font: 500 11px EEMfont, Helvetica, Arial, sans-serif; text-align: center;
}
#ops_simple_list input.ml_xy:disabled { opacity: .45; }
#ops_simple_list input.ml_xy:focus { outline: none; border-color: var(--ops-accent); }
.ml_iconbtn {
    flex: 0 0 auto; cursor: pointer; color: var(--ops-dim);
    padding: 2px 4px; font-size: 12px; line-height: 1;
    border: 1px solid var(--ops-line); border-radius: 4px; background: var(--ops-panel2);
}
.ml_iconbtn:hover { color: var(--ops-accent); border-color: var(--ops-accent); }

/* Transport section — dot | name | fleet | source | dest | cargo out | cargo ret | crew | travel | actions */
.ml_section_tp { margin-top: 10px; }
/* dot · name · fleet · TYPE · source · dest · cargo out · cargo back · move→dest · move→base · travel · runs · actions
   The two crew columns are narrow (a count plus a small "holds now" readout) — see .ml_pax.
   ⚠ Both templates below carry the SAME column list minus the two crew tracks; adding a column means
   editing both, or the header and the rows silently disagree by one and every cell shifts. */
.ml_head_tp, .ml_row_tp {
    display: grid;
    grid-template-columns: 16px minmax(78px, 0.95fr) 0.8fr 0.9fr 1fr 1fr 1.2fr 1.2fr 0.7fr 0.7fr 0.85fr 100px 148px;
    align-items: center; gap: 8px;
}
/* Base Cleaning replaces the cargo/crew inputs with static text — dimmed so it reads as "nothing to set
   here", not as a value someone forgot to fill in. */
.ml_tpclean_note { font-size: 10px; color: var(--ops-dim); font-style: italic; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml_tpclean_dash { color: var(--ops-dim); opacity: 0.5; }
/* The clean-mode crew checkbox reuses .smallcheck + .ml_craft_rawcell for its look, but NOT that
   cell's centring: in a transport row it reads better flush left, under the header text.
   Scoped to .ml_row_tp so crafting rows keep their centred checkbox. */
.ml_row_tp .ml_craft_rawcell { justify-content: flex-start !important; }
/* ---- Loop editor: passenger budget widget (on the EMBARK action's config pane) -------------------
   The whole budget lives in one pane because the numbers are CONSERVED — editing one moves the others,
   so they must all be visible at once. */
.le_pax .le_paxhead, .le_pax .le_paxopts {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    font-size: 11px; color: var(--ops-dim); margin-bottom: 4px;
}
.le_pax input { width: 58px; text-align: right; }
.le_pax .le_paxcap, .le_pax .le_paxreserve { width: 48px; }
.le_paxrows { display: flex; flex-direction: column; gap: 3px; margin: 4px 0; }
.le_paxrow { display: grid; grid-template-columns: 1fr 38px 58px 20px 52px; align-items: center; gap: 6px; }
.le_paxnm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }
/* crew there NOW — "?" means not loaded yet, which is NOT zero (the Crafting row is deleted when a base
   is empty, so absence is ambiguous). Never let it read as a real number. */
.le_paxat { font-size: 10px; text-align: right; color: var(--ops-dim); font-variant-numeric: tabular-nums; }
/* SOURCE crew beside the embark total: idle can be ferried, busy is reserved by running craft jobs, so
   this is what explains an embark taking fewer than the base's headline count (user, 2026-07-30). The
   input beside it needs far less width than the default field. */
.le_paxsrc { font-size: 10px; color: var(--ops-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.le_paxsrc b { font-weight: 600; color: var(--ops-txt); }
.le_paxsrc .le_paxbusy { color: var(--ops-warn); }
.le_paxat_unknown { font-style: italic; opacity: 0.7; }
.le_paxlock { cursor: pointer; text-align: center; opacity: 0.45; font-size: 11px; user-select: none; }
.le_paxlock:hover { opacity: 0.85; }
.le_paxlock.on { opacity: 1; color: var(--ops-accent); }
.le_paxgoal { font-size: 10px; text-align: right; font-variant-numeric: tabular-nums; }

/* Passenger transport is LOCALHOST ONLY for now, so the crew column is omitted entirely in production
   rather than left as a dead empty track. Both the header and the rows carry .ml_tp_nopax together, so
   they can never disagree on the column count. */
.ml_head_tp.ml_tp_nopax, .ml_row_tp.ml_tp_nopax {
    grid-template-columns: 16px minmax(78px, 0.95fr) 0.8fr 0.9fr 1fr 1fr 1.2fr 1.2fr 0.85fr 100px 148px;
}
/* Passenger goal cell: the desired TOTAL crew at the destination + what it holds right now, so the gap
   the loop will close is visible without opening anything. */
.ml_pax { display: inline-flex; align-items: center; gap: 4px; min-width: 0; }
/* Uses .ml_num (the shared numeric-input look, same as the crafting qty/crew/runs fields) — it previously
   carried .ml_sel, whose rule is `select.ml_sel`, so an <input> matched NOTHING and rendered unstyled. */
.ml_paxgoal { flex: 1 1 auto; min-width: 0; }
.ml_paxnow { flex: 0 0 auto; font-size: 10px; color: var(--ops-dim); font-variant-numeric: tabular-nums; }
/* "?" = the crew count has NOT loaded, which is NOT the same as zero — never let it read as a real
   number the user might set a goal against. See _paxCrewAt in ops_loop_list_c4.js. */
.ml_paxnow_unknown { font-style: italic; opacity: 0.7; }
.ml_row_tp { padding: 5px 8px; border-bottom: 1px solid var(--ops-node-line); font-size: 11px; }
.ml_row_tp:last-child { border-bottom: none; }
.ml_row_tp:hover { background: color-mix(in srgb, var(--ops-overlay) 4%, transparent); }
/* cargo checklist rows in the picker popup (checkbox + name + per-run amount) */
.ml_cargorow { display: flex; align-items: center; gap: 6px; padding: 3px 10px; cursor: pointer; }
.ml_cargorow:hover { background: color-mix(in srgb, var(--ops-overlay) 5%, transparent); }
.ml_cargorow .ml_resnm { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml_cargopop input.ml_cargoamt {
    flex: 0 0 auto; width: 58px; box-sizing: border-box;
    background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 4px; padding: 2px 4px;
    font: 500 11px EEMfont, Helvetica, Arial, sans-serif; text-align: right;
}
.ml_cargopop input.ml_cargoamt:focus { outline: none; border-color: var(--ops-accent); }
/* searchable + grouped cargo picker */
.ml_cargosearch {
    display: block; box-sizing: border-box; width: calc(100% - 20px); margin: 6px 10px 4px;
    background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 4px; padding: 4px 7px;
    font: 500 11px EEMfont, Helvetica, Arial, sans-serif;
}
.ml_cargosearch:focus { outline: none; border-color: var(--ops-accent); }
.ml_cargogroup { padding: 4px 10px 2px; font-size: 9px; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.55; font-weight: 600; }
.ml_cargogroup:not(:first-child) { border-top: 1px solid var(--ops-node-line); margin-top: 3px; }
.ml_cargohint { padding: 6px 10px; font-size: 10px; }
.ml_cargorow .ml_resstock { flex: 0 0 auto; font-size: 10px; opacity: 0.6; margin-left: 4px; font-variant-numeric: tabular-nums; }
.ml_cargorow .ml_rescat { flex: 0 0 auto; font-size: 9px; opacity: 0.4; text-transform: uppercase; letter-spacing: 0.03em; margin-left: 4px; }
/* light capacity guard: caps header line + per-item won't-fit flag */
/* Breathing room under the title — 2px let the capacity line collide with the header (user 2026-08-13). */
.ml_cargocaps { padding: 7px 10px 5px; font-size: 10px; color: var(--ops-dim); }
.ml_cargocaps b { color: var(--ops-txt); font-weight: 600; font-variant-numeric: tabular-nums; }
/* Overflow total now lives in the caps header, not per row — the load is atomic, so a per-item split was an
   artifact of pick order and implied a partial outcome that cannot happen. Red, not warn-amber: it BLOCKS. */
.ml_cargocaps .ml_cargowarn { margin-left: 6px; color: var(--ops-red); font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; cursor: help; }
.ml_cargorow .ml_cargowarn { flex: 0 0 auto; margin-left: 4px; font-size: 10px; color: var(--ops-warn); font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
/* "max" — a TINY BUTTON (not bare text) so it reads as clickable, immediately LEFT of the amount input.
   Filled + bordered rather than a link: at 9px a plain coloured word is easy to mistake for a label. */
.ml_cargorow .ml_cargomax {
    flex: 0 0 auto;
    margin-right: 3px;     /* half the old gap — the row is tight, reclaim the space for the amount field */
    padding: 1px 5px;      /* -2px height, -2px width vs the first pass */
    font-family: 'EEMfontLight';   /* matches the app's label type (see basic-ui.css) */
    font-size: 7px;
    line-height: 1.1;
    font-weight: 400;      /* not bold — it sits beside numbers and was shouting */
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ops-accent);
    background: color-mix(in srgb, var(--ops-accent) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--ops-accent) 55%, transparent);
    border-radius: 3px;
    cursor: pointer;
    user-select: none;
    transition: background 0.12s ease, color 0.12s ease;
}
.ml_cargorow .ml_cargomax:hover {
    background: var(--ops-accent);
    color: var(--ops-panel);
    border-color: var(--ops-accent);
}
.ml_cargorow .ml_cargomax:active { transform: translateY(1px); }

/* Crafting section — dot | recipe | starbase | qty | crew | runs | actions */
.ml_section_craft { margin-top: 10px; }
.ml_head_craft, .ml_row_craft {
    display: grid;
    grid-template-columns: 16px minmax(180px, 2.6fr) 1fr 44px 64px 64px 100px 148px;
    align-items: center; gap: 8px;
}
.ml_craft_rawcell { display: flex; justify-content: center; }
.ml_head_craft .ml_h_raw { text-align: center; }
/* Numeric headers (Qty/Crew/Runs) right-align to match their right-aligned .ml_num inputs (border 1px +
   padding-right 5px = 6px inset), so the header text lines up over the value. */
.ml_head_craft .ml_h_num { text-align: right; padding-right: 6px; }
/* small checkbox — compact, accent-tinted, theme-token only */
.smallcheck { 
    width: 13px; height: 13px; 
    margin: 0; 
    cursor: pointer; 
    accent-color: #2F00FF;
    filter:invert(1);
}
.smallcheck:disabled { opacity: .45; cursor: not-allowed; }
.ml_row_craft { padding: 5px 8px; border-bottom: 1px solid var(--ops-node-line); font-size: 11px; }
.ml_row_craft:last-child { border-bottom: none; }
.ml_row_craft:hover { background: color-mix(in srgb, var(--ops-overlay) 4%, transparent); }
.ml_craft_recipe { display: flex; align-items: center; gap: 4px; min-width: 0; }
/* Contextual sub-pickers (ship / building tier / planet / recipe variation) share the recipe column width
   INLINE with the combo — the combo shows the base name, the selects carry the tier/planet/variation. */
.ml_craft_recipe > .ml_combo_inp { flex: 1 1 0; min-width: 40px; }
.ml_subpicks { display: flex; gap: 4px; min-width: 0; flex: 1.5 1 0; }
.ml_subpick {
    min-width: 0; flex: 1 1 0; max-width: 100%;
    background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 4px; padding: 4px 4px;
    font: 500 10px EEMfont, Helvetica, Arial, sans-serif;
}
.ml_subpick:disabled { opacity: .45; cursor: not-allowed; }
.ml_num {
    width: 100%; box-sizing: border-box; background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 4px; padding: 4px 5px;
    font: 500 11px EEMfont, Helvetica, Arial, sans-serif; text-align: right;
}
.ml_num:focus { outline: none; border-color: var(--ops-accent); }
.ml_num:disabled { opacity: 0.5; }
.ml_repeat { display: flex; align-items: center; gap: 4px; }
.ml_repeat_off { opacity: 0.45; }
.ml_repeat_off .ml_num { text-align: center; }
.ml_repeat .ml_num { flex: 1 1 auto; min-width: 0; }
/* ∞ chip LOOK — shared by the crafting row (.ml_inf), the loop rows (.ml_loop_inf) and the editor
   toolbar (.le_inf). The BOX is not set here: inside #ops_simple_list the 24×24 square rule near the
   top of this file owns it (one rule for the ∞ and the action buttons, so they line up), and the
   editor sets its own below. Add a new consumer to those selectors — never a second copy.
   ⚠ .ops_btn's `padding: 4px 10px` makes a WIDE pill, so any consumer that misses a square rule
   renders wrong — which is exactly what .ml_loop_inf did when it shipped (2026-08-15). */
.ml_inf, .ml_loop_inf, #ops_loop_editor .le_inf {
    flex: 0 0 auto; cursor: pointer; color: var(--ops-dim); font-weight: 700;
}
.ml_inf_on { color: var(--ops-accent); border-color: var(--ops-accent); }
/* ∞ on a RUNNING row: inert until ✎ Edit, matching the greyed inputs beside it. A <span> ignores the
   `disabled` attribute, so the lock has to be a class (the click wiring skips it; toggle* also guards). */
.ml_inf_dis { opacity: .4; pointer-events: none; cursor: default; }
/* Name cell = [edit ✎] [name input] [✎ edited]. One grid cell so neither control disturbs the column
   template when it appears; the input takes the slack and keeps its own ellipsis. */
.ml_namecell { display: flex; align-items: center; gap: 4px; min-width: 0; }
.ml_namecell .ml_name_inp { flex: 1 1 auto; min-width: 0; }
.ml_namecell .ops_btn { flex: 0 0 auto; }
/* Edit mode on a running row: the pencil opens it, ✓/✗ save or discard. Accent-tinted so an unlocked row is
   obvious at a glance against the read-only ones around it. The 24×24 box comes from the shared action-button
   rule above (they are single-glyph buttons, exactly like start/stop/dupe/del/↗/∞) — sized in ONE place so
   they can't drift apart. The spacer reserves that same footprint on rows with no button, so every name in
   the column starts at the same x whether or not its row is running. */
.ml_edit_spacer { display: inline-block; flex: 0 0 auto; box-sizing: border-box; width: 24px; height: 24px; }
#ops_simple_list .ml_edit_begin { color: var(--ops-dim); }
#ops_simple_list .ml_edit_begin:hover { color: var(--ops-accent); }
#ops_simple_list .ml_edit_save { color: var(--ops-green); border-color: var(--ops-green); }
#ops_simple_list .ml_edit_cancel { color: var(--ops-red); border-color: var(--ops-red); }
/* "edited" badge — the definition differs from what the run was started with. Informational only: the
   engines apply it at their own boundary (craft cycle / loop wrap), so there is nothing to click. */
.ml_edited {
    flex: 0 0 auto; margin-left: 6px; padding: 0 5px;
    border: 1px solid var(--ops-warn); border-radius: 8px;
    color: var(--ops-warn); background: color-mix(in srgb, var(--ops-warn) 12%, transparent);
    font-size: 9px; line-height: 15px; white-space: nowrap; cursor: help;
}
.ml_lock { flex: 0 0 auto; color: var(--ops-warn); font-size: 11px; cursor: help; }

/* Claimstake section — dot | name | fleet | base | stake | travel | actions */
.ml_section_cs { margin-top: 10px; }
.ml_head_cs, .ml_row_cs {
    display: grid;
    grid-template-columns: 16px minmax(90px, 1.1fr) 0.9fr 1.4fr 1.6fr 1fr 116px;
    align-items: center; gap: 8px;
}
.ml_row_cs { padding: 5px 8px; border-bottom: 1px solid var(--ops-node-line); font-size: 11px; }
.ml_row_cs:last-child { border-bottom: none; }
.ml_row_cs:hover { background: color-mix(in srgb, var(--ops-overlay) 4%, transparent); }
/* Maintainers section — inline config rows: dot | system | fleet | drop-off | status | actions.
   Same grid pattern as the other sections; selects reuse .ml_sel. See project_c4_claimstake_maintainer. */
.ml_section_mnt { margin-top: 10px; }
/* dot | system | stakes | fleet | drop-off | travel | status | actions */
.ml_head_mnt, .ml_row_mnt {
    display: grid;
    grid-template-columns: 16px minmax(70px, 1fr) 44px 0.9fr 1.2fr 1fr 0.85fr 116px;
    align-items: center; gap: 8px;
}
.ml_row_mnt { padding: 5px 8px; border-bottom: 1px solid var(--ops-node-line); font-size: 11px; color: var(--ops-txt); }
.ml_row_mnt:last-child { border-bottom: none; }
.ml_row_mnt:hover { background: color-mix(in srgb, var(--ops-overlay) 4%, transparent); }
.ml_samesys { display: inline-flex; align-items: center; min-width: 0; padding: 4px 6px; font-size: 11px; font-style: italic; color: var(--ops-dim); }
/* Height placeholder for a section header with no "+ Add" button (Maintainers). It IS a .ml_add/.ops_btn,
   just invisible, so it reserves that button's exact box — a hardcoded min-height would silently drift the
   moment the button's padding or font changes. Never give this one a click handler. */
.ml_add_ghost { visibility: hidden; pointer-events: none; }

/* Starbase Upgrading section — fleet-free per-starbase automation, collapsed by default (see
   LIST_COLLAPSE_DEFAULT in ops_loop_list_c4.js). Same grid pattern as the sections above:
   dot | starbase | now | up to | material | batch | crew | status | actions. Only two action buttons
   (▶/■ + ✕), so the ACTIONS column is narrower than the 116px three-button ones — the 24×24 button box
   itself still comes from the shared rule near the top of this file. */
.ml_section_upg { margin-top: 10px; }
/* dot | starbase | now | up to | material | max batch | crew | duration | status | actions */
.ml_head_upg, .ml_row_upg {
    display: grid;
    grid-template-columns: 16px minmax(90px, 1.3fr) 52px 1fr 1.3fr 74px 62px 62px 1fr 64px;
    align-items: center; gap: 8px;
}
.ml_row_upg { padding: 5px 8px; border-bottom: 1px solid var(--ops-node-line); font-size: 11px; color: var(--ops-txt); }
.ml_row_upg:last-child { border-bottom: none; }
.ml_row_upg:hover { background: color-mix(in srgb, var(--ops-overlay) 4%, transparent); }
.ml_head_upg .ml_h_num { text-align: right; padding-right: 6px; }
/* DURATION — read-only estimate for ONE batch at the configured crew. Reads "—" unless BOTH max batch and
   crew are pinned: with either at 0 the size or the staffing varies per pass, so a number there would be
   a guess. */
.ml_upg_dur { text-align: right; padding-right: 6px; white-space: nowrap; }
/* HIDDEN FOR NOW (user 2026-08-19). Both halves are required: `display:none` removes the CELLS, but grid
   tracks come from the template, so the column would still reserve its width and leave a gap. Drop the
   `ml_upg_nodur` class in upgradeSectionHtml to reveal the column — no other change needed. */
.ml_upg_nodur .ml_h_dur, .ml_upg_nodur .ml_upg_dur { display: none; }
.ml_upg_nodur .ml_head_upg, .ml_upg_nodur .ml_row_upg {
    grid-template-columns: 16px minmax(90px, 1.3fr) 52px 1fr 1.3fr 74px 62px 1fr 64px;
}

/* ############################################################################
   FOLDED PER-VIEW STYLES (2026-07-15 UI unification — one CSS file).
   Previously ops_loop_editor.css / ops_flows.css / ops_crafting_editor.css /
   ops_overview.css. The loop editor is the canonical chrome; shared .ops_* classes
   above mirror it. Per-view INTERNALS (radial stage, flows columns, craft windows,
   schema svg) are kept as-is below, scoped to their host id.
   ############################################################################ */

/* ==== from ops_loop_editor.css (radial loop editor) ==== */
/* C4 OPERATIONS — Loop editor (radial loop configurator) styles.
 * Mounted into #ops_loop_editor at runtime by loopEditor_mount(); the host is hidden
 * (display:none) until opsLoopMode('loop') reveals it.
 * DESIGN TOKENS live in css/ops_shared.css on #ops_container scope (--ops-bg / --ops-panel /
 * --ops-accent / etc.) — do NOT redefine here. A change in the shared file restyles both the
 * loop editor and the overview simultaneously. */

#ops_loop_editor {
    display: none;
    flex-direction: column;
    flex: 1 1 0;
    min-height: 0;
    background: var(--ops-bg);
    color: var(--ops-txt);
    font: 13px/1.45 EEMfontLight, Helvetica, Arial, sans-serif;
    border: 1px solid var(--ops-line);
}
#ops_loop_editor * { box-sizing: border-box; }

/* ---- top toolbar ---- */
#ops_loop_editor .le_bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    background: var(--ops-panel);
    border-bottom: 1px solid var(--ops-line);
}
#ops_loop_editor .le_h1 {
    font-size: 13px;
    color: var(--ops-accent);
    font-weight: 600;
    letter-spacing: .5px;
    font-family: EEMfont, Helvetica, Arial, sans-serif;
}
#ops_loop_editor b,
#ops_loop_editor .nm,
#ops_loop_editor .pane > h2 {
    font-family: EEMfont, Helvetica, Arial, sans-serif;
}
#ops_loop_editor .le_lbl { color: var(--ops-dim); font-size: 11px; }
#ops_loop_editor select,
#ops_loop_editor input,
#ops_loop_editor button {
    background: var(--ops-panel2);
    color: var(--ops-txt);
    border: 1px solid var(--ops-line);
    border-radius: 4px;
    padding: 4px 8px;
    font: inherit;
}
#ops_loop_editor select { min-width: 150px; }
#ops_loop_editor button { cursor: pointer; }
#ops_loop_editor button:hover { border-color: var(--ops-accent); color: var(--ops-accent); }
/* Loop/Linear segmented toggle — mirrors the flows Simulation/Live control (.ofl_modes/.ofl_mode). */
#ops_loop_editor .le_modes { display: inline-flex; border: 1px solid var(--ops-line); border-radius: 4px; overflow: hidden; }
#ops_loop_editor .le_mode_opt { padding: 4px 12px; cursor: pointer; color: var(--ops-dim); background: var(--ops-panel2); font-size: 11px; user-select: none; }
#ops_loop_editor .le_mode_opt + .le_mode_opt { border-left: 1px solid var(--ops-line); }
#ops_loop_editor .le_mode_opt:hover { background: var(--ops-panel2-hi); color: var(--ops-txt); }
#ops_loop_editor .le_mode_opt.on { background: var(--ops-accent); color: var(--ops-ink); }
/* Run count beside Launch — the toolbar twin of the list view's .ml_repeat pair (number + ∞). Same
   tokens as .ml_num / .ml_inf so the two surfaces read identically; only the width differs, since the
   toolbar is horizontal rather than a fixed grid track. Empty input + lit ∞ == no cap (runs forever).
   Dimmed whole in Linear mode: a linear loop is one pass, so there is nothing to cap. */
#ops_loop_editor .le_repeat { display: inline-flex; align-items: center; gap: 4px; }
#ops_loop_editor .le_repeat_off { opacity: 0.45; }
#ops_loop_editor .le_runs {
    width: 58px; box-sizing: border-box; background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 4px; padding: 4px 5px;
    font: 500 11px EEMfont, Helvetica, Arial, sans-serif; text-align: right;
}
#ops_loop_editor .le_runs:focus { outline: none; border-color: var(--ops-accent); }
#ops_loop_editor .le_runs:disabled { opacity: 0.5; }
/* Same 24×24 square as the list chips — the rule near the top of this file is scoped to
   #ops_simple_list, which does not cover the editor toolbar. */
#ops_loop_editor .le_inf {
    box-sizing: border-box; width: 24px; height: 24px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 12px; line-height: 1; font-weight: 500;
}
#ops_loop_editor .le_inf.le_inf_on { color: var(--ops-accent); border-color: var(--ops-accent); }
#ops_loop_editor .le_inf.le_dis { cursor: default; opacity: 0.5; }
/* Strand-safety notice for fuel unloads. Base = warn tint; .le_strand = red (would strand); .le_ok =
   subtle dim confirmation (within the safe headroom). */
#ops_loop_editor .le_warn { margin-top: 6px; padding: 5px 8px; font-size: 11px; line-height: 1.4; color: var(--ops-warn); background: var(--ops-warn-20); border-radius: 4px; }
#ops_loop_editor .le_warn b { color: var(--ops-warn); font-weight: 600; }
#ops_loop_editor .le_warn.le_strand { color: var(--ops-red); background: var(--ops-red-12); }
#ops_loop_editor .le_warn.le_strand b { color: var(--ops-red); }
#ops_loop_editor .le_warn.le_ok { color: var(--ops-dim); background: transparent; padding: 3px 2px; margin-top: 4px; }
#ops_loop_editor .le_warn.le_ok b { color: var(--ops-txt); }
#ops_loop_editor .le_hint { color: var(--ops-dim); font-size: 11px; }
#ops_loop_editor .le_sp { flex: 1; }
#ops_loop_editor .le_name { min-width: 120px; width: 150px; }

#ops_loop_editor .le_launch {
    border-color: var(--ops-launch);
    color: var(--ops-launch);
}
#ops_loop_editor .le_launch:hover:not(:disabled) {
    background: var(--ops-launch-12);
    color: var(--ops-launch);
}
#ops_loop_editor .le_btn_stop {
    border-color: var(--ops-red);
    color: var(--ops-red);
}
#ops_loop_editor .le_btn_stop:hover:not(:disabled) {
    background: var(--ops-red-12);
    color: var(--ops-red);
}
#ops_loop_editor .le_launch:disabled,
#ops_loop_editor .le_btn_stop:disabled {
    opacity: .35;
    pointer-events: none;
    border-color: var(--ops-line);
    color: var(--ops-dim);
}

/* ---- saved-loops tabs strip ---- */
#ops_loop_editor .le_tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    padding: 6px 12px;
    background: var(--ops-panel);
    border-bottom: 1px solid var(--ops-line);
}
#ops_loop_editor .le_tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border: 1px solid var(--ops-line);
    border-radius: 13px;
    background: var(--ops-panel2);
    color: var(--ops-dim);
    cursor: pointer;
    font-size: 11px;
    white-space: nowrap;
}
#ops_loop_editor .le_tab:hover { border-color: var(--ops-accent); color: var(--ops-txt); }
#ops_loop_editor .le_tab.on {
    border-color: var(--ops-accent);
    color: var(--ops-accent);
    background: var(--ops-accent-08);
}
#ops_loop_editor .le_tab .lf { color: var(--ops-dim); }
#ops_loop_editor .le_tab .tx { color: var(--ops-dim); font-size: 10px; }
#ops_loop_editor .le_tab .tx:hover { color: var(--ops-red); }
#ops_loop_editor .le_tab.new { border-style: dashed; }
#ops_loop_editor .le_tab .run_dot {
    display: inline-block;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--ops-green);
    box-shadow: 0 0 6px var(--ops-green);
    animation: ops_run_pulse 1.6s ease-in-out infinite;   /* shared keyframe (was identical le_run_pulse) */
}
/* Paused loop — steady blue dot (no pulse), so paused reads distinct from actively-running green. */
#ops_loop_editor .le_tab .run_dot.paused {
    background: var(--ops-info);
    box-shadow: 0 0 6px var(--ops-info);
    animation: none;
}
/* Blocked loop — steady red dot: enrolled but stuck (needs attention), NOT smoothly running-green. */
#ops_loop_editor .le_tab .run_dot.blocked {
    background: var(--ops-red);
    box-shadow: 0 0 6px var(--ops-red);
    animation: none;
}
#ops_loop_editor .le_tab .dirty_mark { color: var(--ops-accent); font-weight: 700; margin-left: 2px; }
/* Op-relationship status text beside the Launch/Stop buttons (running / queued-takeover / fleet-busy). */
#ops_loop_editor .le_opstate { font-size: 10px; margin-left: 8px; white-space: nowrap; color: var(--ops-dim); }
#ops_loop_editor .le_opstate.run { color: var(--ops-launch); }
#ops_loop_editor .le_opstate.queued { color: var(--ops-info); }
#ops_loop_editor .le_opstate.busy { color: var(--ops-dim); }
#ops_loop_editor .le_opstate.blocked { color: var(--ops-red); }
#ops_loop_editor .le_tab.dirty:not(.on) { color: var(--ops-txt); }
/* le_run_pulse merged into the shared @keyframes ops_run_pulse (identical) near the top of this file. */
#ops_loop_editor button.le_revert {
    background: var(--ops-panel2);
    color: var(--ops-accent);
    border-color: var(--ops-accent);
}
#ops_loop_editor button.le_revert:hover:not(:disabled) { background: var(--ops-accent-12); }
#ops_loop_editor button#le_save.dirty {
    background: var(--ops-accent);
    color: var(--ops-ink);
    border-color: var(--ops-accent);
    font-weight: 700;
}
#ops_loop_editor button#le_save.dirty:hover:not(:disabled) { background: var(--ops-accent2); }

#ops_loop_editor #le_status {
    padding: 3px 9px;
    border-radius: 10px;
    font-weight: 600;
    font-size: 11px;
}
#ops_loop_editor #le_status.ok { background: var(--ops-green-12); color: var(--ops-green); }
#ops_loop_editor #le_status.bad { background: var(--ops-red-12); color: var(--ops-red); }

/* ---- main layout: flow | stage | side ---- */
#ops_loop_editor .le_main { display: flex; flex: 1; min-height: 0; }
#ops_loop_editor .le_flow {
    width: 250px;
    min-width: 250px;
    border-right: 1px solid var(--ops-line);
    background: var(--ops-panel);
    overflow: auto;
}
#ops_loop_editor .le_stage {
    flex: 1;
    position: relative;
    overflow: auto;
    background: radial-gradient(circle at 1px 1px, var(--ops-grid-dot) 1px, transparent 0) 0 0 / 24px 24px;
}
#ops_loop_editor #le_canvas { position: relative; width: 920px; height: 850px; margin: 6px auto; }
#ops_loop_editor svg#le_ring { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
#ops_loop_editor .le_ringline { fill: none; stroke: var(--ops-ring); stroke-width: 2; }
#ops_loop_editor .le_arc { fill: none; stroke: var(--ops-arc); stroke-width: 2; }
/* Dashed arc for legs whose two stops sit in the SAME solar system — no chain movement ix is
   dispatched for the "leg", so we signal "no movement here" with a dashed stroke. The mvEl
   triangle is also skipped for these legs (see mvEl in ops_loop_editor_c4.js). */
#ops_loop_editor .le_arc.le_arc_insys { stroke-dasharray: 1 4; }
#ops_loop_editor .le_arc.le_arc_broken { stroke: var(--ops-red); stroke-width: 2.5; cursor: help; }

/* Broken-legs explainer rows in the flow pane */
#ops_loop_editor .brk_sec .brk_h { color: var(--ops-red); }
#ops_loop_editor .brk_row { display: flex; flex-direction: column; padding: 2px 0; border-top: 1px dashed var(--ops-line); margin-top: 2px; }
#ops_loop_editor .brk_row:first-of-type { border-top: 0; margin-top: 0; }
#ops_loop_editor .brk_leg { color: var(--ops-txt); font-size: 11px; font-weight: 600; }
#ops_loop_editor .brk_msg { color: var(--ops-red); font-size: 10px; line-height: 1.3; margin-top: 1px; }

/* ---- dev-only breakdown pane (left, under the flow summary) ---- */
#ops_loop_editor .dimsub { color: var(--ops-dim); font-size: 9px; text-transform: none; letter-spacing: 0; font-weight: 400; }
#ops_loop_editor #le_breakdownOut {
    margin: 0;
    white-space: pre-wrap;
    word-break: break-word;
    font: 10px/1.35 EEMfontLight, ui-monospace, Menlo, Consolas, monospace;
    color: var(--ops-dim);
    background: var(--ops-bg-deep);
    padding: 6px 8px;
    border-radius: 4px;
    max-height: 100%;
    overflow: auto;
}

/* ---- inline validation badge in the LOOP pane header ---- */
#ops_loop_editor .le_val_inline {
    font-size: 10px;
    font-weight: 400;
    color: var(--ops-dim);
    letter-spacing: 0;
    text-transform: none;
    margin-left: 8px;
}
#ops_loop_editor .le_val_inline .vok { color: var(--ops-green); }
#ops_loop_editor .le_val_inline .verr { color: var(--ops-red); display: inline; }
#ops_loop_editor .le_val_inline .dim { color: var(--ops-dim); }

#ops_loop_editor .le_side {
    width: 250px;
    min-width: 250px;
    border-left: 1px solid var(--ops-line);
    background: var(--ops-panel);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* ---- side panes (derived JSON / validation / dry-run) ---- */
#ops_loop_editor .pane { border-bottom: 1px solid var(--ops-line); }
#ops_loop_editor .pane.gw { flex: 1; overflow: auto; }
#ops_loop_editor .pane > h2 {
    margin: 0;
    padding: 6px 10px;
    font-size: 11px;
    letter-spacing: .6px;
    color: var(--ops-dim);
    text-transform: uppercase;
    background: var(--ops-panel2);
}
#ops_loop_editor .pane .pb { padding: 8px 10px; overflow: auto; }
#ops_loop_editor #le_jsonOut {
    white-space: pre;
    font-size: 11px;
    color: var(--ops-dim);
    max-height: 170px;
    overflow: auto;
}
#ops_loop_editor .dim { color: var(--ops-dim); font-size: 11px; }
#ops_loop_editor .verr { color: var(--ops-red); }
#ops_loop_editor .vok { color: var(--ops-green); }

/* ---- per-loop Settings pane (right column) ---- */
#ops_loop_editor .le_settings input { accent-color: var(--ops-accent); cursor: pointer; }
#ops_loop_editor .le_set_group { margin: 0 0 6px 0; }
#ops_loop_editor .le_set_group + .le_set_group { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--ops-line-soft); }
#ops_loop_editor .le_set_glabel { font-size: 11px; font-weight: 600; color: var(--ops-txt-hi); }
#ops_loop_editor .le_set_ghint { font-size: 10px; color: var(--ops-dim); margin: 2px 0 6px 0; line-height: 1.35; }
/* A settings group that cannot apply to the current loop: dimmed, non-interactive, with an inline note
   saying why. Better than hiding it — the option still exists, it just has nothing to govern here. */
#ops_loop_editor .le_set_na .le_set_opt { opacity: 0.4; cursor: not-allowed; }
#ops_loop_editor .le_set_na .le_set_glabel, #ops_loop_editor .le_set_na .le_set_ghint { opacity: 0.55; }
#ops_loop_editor .le_set_na_note { display: none; }
#ops_loop_editor .le_set_na .le_set_na_note {
    display: block; font-size: 11px; line-height: 1.35; margin: 2px 0 4px;
    color: var(--ops-dim); font-style: italic;
}
#ops_loop_editor .le_set_opt { display: flex; align-items: flex-start; cursor: pointer; padding: 3px 0; font-size: 12px; color: var(--ops-txt); }
#ops_loop_editor .le_set_opt input { flex: 0 0 auto; margin: 2px 6px 0 0; }
#ops_loop_editor .le_set_body { display: flex; flex-direction: column; }
#ops_loop_editor .le_set_name { }
#ops_loop_editor .le_set_hint { font-size: 10px; color: var(--ops-dim); margin-top: 1px; line-height: 1.3; }
#ops_loop_editor .le_set_default { display: block; cursor: pointer; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--ops-line-soft); font-size: 11px; color: var(--ops-dim); }
#ops_loop_editor .le_set_default input { vertical-align: -1px; margin-right: 6px; }

#ops_loop_editor #le_dryOut .drow {
    padding: 1px 0 1px 14px;
    position: relative;
    font-size: 12px;
}
#ops_loop_editor #le_dryOut .drow::before {
    content: "";
    position: absolute;
    left: 4px;
    top: 8px;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--ops-loop-1);
}
#ops_loop_editor #le_dryOut .drow.dispatch::before { background: var(--ops-green); }
#ops_loop_editor #le_dryOut .drow.block { color: var(--ops-red); }
#ops_loop_editor #le_dryOut .out { color: var(--ops-dim); margin-top: 5px; }

/* ---- flow pane (per-cycle estimates) ---- */
#ops_loop_editor .le_flow .fsec { margin-bottom: 14px; }
#ops_loop_editor .le_flow .fh {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: .6px;
    color: var(--ops-dim);
    margin: 0 0 4px;
}
#ops_loop_editor .le_flow .frow {
    display: flex;
    align-items: center;
    padding: 3px 0;
    font-size: 12px;
    border-bottom: 1px solid var(--ops-line-soft);
}
#ops_loop_editor .le_flow .frow .fn { flex: 1; }
#ops_loop_editor .le_flow .frow .fv { min-width: 52px; text-align: right; }
#ops_loop_editor .le_flow .frow .fd {
    min-width: 62px;
    text-align: right;
    color: var(--ops-dim);
    font-variant-numeric: tabular-nums;
    font-size: 11px;
}
#ops_loop_editor .le_flow .fv { font-variant-numeric: tabular-nums; font-weight: 600; }
#ops_loop_editor .le_flow .ftot {
    border-top: 1px solid var(--ops-line);
    border-bottom: none;
    margin-top: 4px;
    color: var(--ops-dim);
}
#ops_loop_editor .le_flow .fhd {
    border-bottom: 1px solid var(--ops-line);
    color: var(--ops-dim);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: .4px;
}
#ops_loop_editor .le_flow .fhd .fv,
#ops_loop_editor .le_flow .fhd .fd { font-weight: 400; }
#ops_loop_editor .le_flow .fruns { 
    font-size: 12px; 
    color: var(--ops-txt); 
    margin-bottom: 3px; 
}
#ops_loop_editor .le_flow .fruns b { 
    color: var(--ops-accent); 
    font-variant-numeric: tabular-nums; 
}
#ops_loop_editor .le_flow .fcyc { 
    font-size: 11px; 
    color: var(--ops-dim); 
    margin-bottom: 9px; 
}
#ops_loop_editor .le_flow .fcyc .sub { 
    color: var(--ops-dim2); 
}
#ops_loop_editor .le_flow .fnone { color: var(--ops-dim); font-size: 11px; }
#ops_loop_editor .est {
    color: var(--ops-type-asteroid);
    font-size: 9px;
    border: 1px solid var(--ops-asteroid-border);
    border-radius: 6px;
    padding: 0 4px;
}

/* ---- radial canvas: fleet at center, stops around the ring ---- */
#ops_loop_editor .le_fleet {
    position: absolute;
    width: 150px;
    text-align: center;
    transform: translate(-50%, -50%);
}
#ops_loop_editor .le_fleet .disc {
    width: 120px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ops-accent);
    font-weight: 700;
    font-size: 20px;
}
#ops_loop_editor .le_fleet .disc img {
    width: 120px;
    height: auto;
    object-fit: contain;
    display: block;
}
#ops_loop_editor .le_fleet .nm { font-weight: 600; letter-spacing: .5px; color: var(--col-fleet); }
#ops_loop_editor .le_fleet .sub { color: var(--ops-dim); font-size: 11px; }

/* CHROME (bg / border / radius / overflow) comes from the shared .ops_node the element also
   carries (class="le_stop ops_node"); le_stop keeps only its layout + shadow + cursor. */
#ops_loop_editor .le_stop {
    position: absolute;
    width: 150px;
    transform: translate(-50%, -50%);
    box-shadow: 0 5px 16px var(--ops-shadow-45);
    cursor: pointer;
}
#ops_loop_editor .le_stop.sel { border-color: var(--ops-accent); }
#ops_loop_editor .le_stop.unplaced { border-style: dashed; border-color: var(--ops-muted); }
/* ★ le_scanstarved — this stop's scans cannot be funded: earlier scan stops already claim the whole kit
   budget. Red because it is the same class of problem as le_cantmine (the stop does not do what the user
   configured), and it must be legible on the RING without opening the card. `sel` still wins, so
   selecting the stop does not hide the accent. */
#ops_loop_editor .le_stop.le_scanstarved { border-color: var(--ops-red); }
#ops_loop_editor .le_stop.le_scanstarved.sel { border-color: var(--ops-accent); box-shadow: 0 0 0 1px var(--ops-red), 0 5px 16px var(--ops-shadow-45); }
#ops_loop_editor .le_stop.unplaced .sb .a { color: var(--ops-dim); font-style: italic; }
/* Header flex/bg/border + .dot/.ty/.co + .sb padding/font/color + .sb .a color all come from the
   shared .ops_node_head / .ops_node_dot / .ops_node_type / .ops_node_meta / .ops_node_body /
   .ops_node_name (element carries both class sets). Only the le_stop-specific deltas remain. */
#ops_loop_editor .le_stop .sh { padding: 5px 7px; }
#ops_loop_editor .le_stop .sb { min-height: 20px; }
/* Phase 4 — per-base net material summary on the node (surplus green / deficit red, /day). */
#ops_loop_editor .le_stop .le_stopflow {
    border-top: 1px solid var(--ops-node-line);
    padding: 4px 7px;
    display: flex;
    flex-direction: column;
    gap: 1px;
}
#ops_loop_editor .le_stop .le_stopflow .fl {
    font-size: 9.5px;
    line-height: 1.4;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#ops_loop_editor .le_stop .le_stopflow .fl .ar { margin-right: 3px; font-size: 7px; vertical-align: 1px; }
#ops_loop_editor .le_stop .le_stopflow .fl.pos { color: var(--ops-green); }
#ops_loop_editor .le_stop .le_stopflow .fl.neg { color: var(--ops-red); }
#ops_loop_editor .le_stop .le_stopflow .fl.mo { color: var(--ops-dim2); font-size: 8.5px; font-style: italic; }
/* ★★ "fleet cannot mine" — the selected fleet's chain cargoStats.miningRate is 0, so a mine action here
   extracts nothing and the loop can never start (incident 2026-08-13: Star Atlas changed ship stats; see
   BLOCK.cannot_mine in js/ops_loop_manifest_c4.js). Shown on the stop CARD and again inside the mine
   action's CONFIG pane, since the card can sit behind the config window. --ops-red IS --col-stopped. */
#ops_loop_editor .le_stop .le_cantmine {
    border-top: 1px solid color-mix(in srgb, var(--ops-red) 40%, var(--ops-node-line));
    padding: 3px 7px;
    color: var(--ops-red);
    font-size: 9.5px;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* B2 — scan dwell readout: how long the fleet is parked (and therefore a target) at a scan spot.
   Informational, so it uses the muted/dim vocabulary rather than a warning colour. */
/* B3 arc — the CONDITIONAL back-edge from the last scan stop to the first. Dashed because it is only
   taken when the hold is still under the threshold; --ops-info so it reads as a different KIND of edge
   from the solid movement legs (--ops-arc) and never as an error (--ops-red).
   SVG attributes do not resolve var(), so every colour here is set through CSS, never setAttribute. */
#ops_loop_editor .le_offarc {
    fill: none;
    stroke: var(--ops-info);
    stroke-width: 1.5;
    stroke-dasharray: 6,5;
    opacity: .85;
}
#ops_loop_editor .le_offchip { cursor: pointer; }
#ops_loop_editor .le_offchip rect {
    fill: var(--ops-panel2);
    stroke: var(--ops-info);
    stroke-width: 1;
}
#ops_loop_editor .le_offchip text {
    fill: var(--ops-info);
    font-size: 9.5px;
    font-weight: 600;
    pointer-events: none;
}
#ops_loop_editor .le_offchip:hover rect { fill: var(--ops-panel2-hi); }
#ops_loop_editor .le_offchip:hover text { fill: var(--ops-txt-hi); }
/* B3 — offload-exit block on the last scan stop's config card. */
#ops_container .le_cfg .le_offexit { margin: 6px 0 2px; padding-top: 5px; border-top: 1px solid var(--ops-line-soft); }
/* Checkbox hard against the left edge, label on ONE line. `.ml_craft_rawcell` centres its checkbox and
   `.smallcheck` is shared by several panes, so pin the geometry here with !important rather than editing
   either shared class -- same treatment the clean-mode crew checkbox needed (.ml_row_tp above). */
#ops_container .le_cfg .le_offexit label {
    display: flex; align-items: center; gap: 6px;
    justify-content: flex-start !important;
    cursor: pointer; font-size: 10.5px; color: var(--ops-txt);
    white-space: nowrap;
}
/* ⚠ `#ops_container .le_cfg input { width: 100% }` (below) beats `.smallcheck`'s 13px on specificity, so
   the checkbox stretched to the FULL label width and shoved the text clean off the end -- it looked like
   a centred checkbox with no label. Measured: input 220px wide, span 0px at offsetLeft 226.
   `.le_cond input[type="checkbox"]` already had to solve this exact thing; same treatment here, and the
   attribute selector is what carries the extra specificity (no !important needed for width). */
#ops_container .le_cfg .le_offexit label > input[type="checkbox"] {
    flex: 0 0 auto;
    width: 13px; height: 13px; min-width: 0;
    margin: 0 !important; padding: 0;
}
/* 0 1 auto, NOT 1 1 auto: size to the text and let the label be as wide as it needs. */
#ops_container .le_cfg .le_offexit label > span { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
#ops_container .le_cfg .le_offexit_row { margin-top: 4px; }
#ops_container .le_cfg .le_offexit_off { margin-top: 3px; font-size: 9.5px; }
#ops_container .le_cfg .le_scandwell {
    margin: 3px 0 2px;
    padding: 2px 2px;
    color: var(--ops-dim);
    font-size: 10px;
    line-height: 1.4;
}
#ops_container .le_cfg .le_scandwell b { color: var(--ops-txt); font-weight: 600; }
#ops_container .le_cfg .le_cantmine_cfg {
    margin: 4px 0 2px;
    padding: 3px 6px;
    border: 1px solid color-mix(in srgb, var(--ops-red) 45%, transparent);
    border-radius: 3px;
    background: color-mix(in srgb, var(--ops-red) 12%, transparent);
    color: var(--ops-red);
    font-size: 10px;
    font-weight: 600;
}
/* Over capacity is NOT a blocker any more — the load is trimmed from the bottom and the loop still runs, so
   it reads as a WARNING (amber ⚠), not the red ✕ used for "cannot start at all". Same geometry override. */
#ops_loop_editor .le_stop .le_cantmine.le_willtrim,
#ops_container .le_cfg .le_cantmine_cfg.le_willtrim {
    color: var(--ops-warn);
    border-color: color-mix(in srgb, var(--ops-warn) 45%, transparent);
    background: color-mix(in srgb, var(--ops-warn) 12%, transparent);
}
#ops_loop_editor .le_stop .le_cantmine.le_willtrim { background: none; border-top-color: color-mix(in srgb, var(--ops-warn) 40%, var(--ops-node-line)); }
#ops_container .ops_dot.ml_dot_willtrim { color: var(--ops-warn); }
/* ml_dot_cantpax — RED like cantmine (the loop genuinely cannot complete) but a TRIANGLE like willtrim.
   It joins the shared geometry rule below and takes its `color: var(--ops-red)` default; only
   ml_dot_willtrim overrides that to amber. */
#ops_container .ops_dot.ml_dot_cantmine,
#ops_container .ops_dot.ml_dot_cantpax,
#ops_container .ops_dot.ml_dot_willtrim {
    width: auto;
    height: auto;
    border-radius: 0;
    background: none;
    box-shadow: none;
    color: var(--ops-red);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    cursor: help;
}

#ops_loop_editor .le_plus {
    position: absolute;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--ops-panel2);
    border: 1.5px dashed var(--ops-muted);
    color: var(--ops-dim);
    font-size: 18px;
    line-height: 1;
}
#ops_loop_editor .le_plus:hover {
    border-color: var(--ops-accent);
    color: var(--ops-accent);
    border-style: solid;
}

/* ---- outward + button on placed base stops (adds crafting/condition side-jobs) ----
   Solid circle (distinct from the ring's dashed '+' which adds a new STOP) placed on one of
   the stop's cardinal edges — top/right/bottom/left, never diagonal. Sits with a small gap
   past the stop card border so it doesn't touch the card. */
#ops_loop_editor .le_att_plus {
    position: absolute;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--ops-node-bg);
    border: none;
    color: var(--ops-dim);
    font-size: 14px;
    line-height: 1;
    z-index: 3;
    transition: background-color .12s ease, color .12s ease;
}
#ops_loop_editor .le_att_plus span { display: block; line-height: 1; margin-top: -1px; }
#ops_loop_editor .le_att_plus:hover {
    background: var(--ops-node-head);
    color: var(--ops-accent);
}

/* ---- shared crafting container hanging outward from a base stop ----
   One container per starbase (not per loop): lists every crafting job registered against the
   starbase key. Each row is a job — click the row to edit, click the [x] to remove. Jobs are
   backed by the shared per-starbase store so they appear in every loop that visits the
   starbase. */
#ops_loop_editor .le_att {
    position: absolute;
    width: 180px;
    transform: translate(-50%, -50%);
    background: var(--ops-node-bg);
    border: 1px solid var(--ops-node-line);
    border-radius: 9px;
    box-shadow: 0 5px 16px var(--ops-shadow-45);
    overflow: hidden;
    z-index: 2;
}
#ops_loop_editor .le_att .ah {
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 4px 8px;
    border-bottom: 1px solid var(--ops-node-line);
    font-size: 9px;
    text-transform: uppercase;
    color: var(--ops-dim);
    background: var(--ops-node-head);
}
#ops_loop_editor .le_att .ah .k { flex: 1; letter-spacing: 0.5px; color: var(--ops-accent); font-weight: 600; }
#ops_loop_editor .le_att .ab {
    padding: 2px 0;
    font-size: 11px;
    color: var(--ops-txt);
    /* Height cap + scroll are applied INLINE by craftingContainerEl() only when the job count
       exceeds the visible cap (ATT_MAX_VISIBLE_ROWS / available space). Containers at or under
       the cap stay content-sized with no scrollbar. */
}
#ops_loop_editor .le_att .ab .jrow {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    cursor: pointer;
    border-left: 2px solid transparent;
    transition: background-color .12s ease, border-color .12s ease;
}
#ops_loop_editor .le_att .ab .jrow:hover { background: var(--ops-overlay-04); }
#ops_loop_editor .le_att .ab .jrow.sel {
    background: var(--ops-sel-08);
    border-left-color: var(--ops-accent);
}
/* .rn ellipsis/line-height/color, .qc, and .x(:hover) come from the shared .ops_row_name /
   .ops_row_qty / .ops_row_del (cells carry both class sets). .rn keeps its flex override; the
   italic "no recipe" .rn .dim stays. */
#ops_loop_editor .le_att .ab .jrow .rn { flex: 1; }
#ops_loop_editor .le_att .ab .jrow .rn .dim { color: var(--ops-dim); font-style: italic; }

/* ---- crafting attachment config pane ---- */
/* The recipe dropdown (.recipeList) drops BELOW its input as an absolutely-positioned child. The base
   .le_cfg card sets overflow:hidden (for its rounded corners), which CLIPPED the dropdown at the card's
   bottom edge — matches below the fold (e.g. "Tin") couldn't be scrolled into view. Un-clip THIS card
   variant only (higher specificity than .le_cfg so it wins regardless of source order); the card's own
   content is padded, so the rounded corners still read clean. The dropdown keeps its own max-height +
   overflow-y:auto, so long match lists scroll internally. */
#ops_container .le_cfg.le_cfg_att { overflow: visible; }
#ops_container .le_cfg_att .hd {
    padding: 8px 12px;
    border-bottom: 1px solid var(--ops-line);
    display: flex;
    align-items: baseline;
    gap: 10px;
}
#ops_container .le_cfg_att .hd .ty {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--ops-accent);
    font-weight: 600;
}
#ops_container .le_cfg_att .hd .loc { color: var(--ops-dim); font-size: 12px; flex: 1; }
#ops_container .le_cfg_att .hd .close {
    color: var(--ops-dim);
    cursor: pointer;
    padding: 0 4px;
    font-size: 14px;
    line-height: 1;
    align-self: center;
    transition: color .12s ease;
}
#ops_container .le_cfg_att .hd .close:hover { color: var(--ops-txt); }
#ops_container .le_cfg_att .bd { padding: 10px 12px; }
#ops_container .le_cfg_att .bd label {
    display: block;
    font-size: 10px;
    text-transform: uppercase;
    color: var(--ops-dim);
    margin-bottom: 3px;
    margin-top: 8px;
    letter-spacing: 0.5px;
}
#ops_container .le_cfg_att .bd label:first-child { margin-top: 0; }
#ops_container .le_cfg_att .bd select,
#ops_container .le_cfg_att .bd input {
    width: 100%;
    background: var(--ops-bg-sunken);
    border: 1px solid var(--ops-line);
    color: var(--ops-txt);
    padding: 4px 6px;
    border-radius: 4px;
    font-size: 12px;
    box-sizing: border-box;
}
#ops_container .le_cfg_att .bd .row2 {
    display: flex;
    gap: 10px;
}
#ops_container .le_cfg_att .bd .row2 > div { flex: 1; }
/* Infinite toggle shares a row with the Runs count. Bottom-align the row so the checkbox lines up
   with the Runs INPUT box (not its label above) — the checkbox and input are the same height, so
   flex-end makes their centers coincide (measured delta 0). When Infinite is on, the Runs cell stays
   in place but greys out (.is_off) so the layout never shifts. */
#ops_container .le_cfg_att .bd .cj_runs { align-items: flex-end; }
#ops_container .le_cfg_att .bd .cj_runcount.is_off { opacity: .4; }
#ops_container .le_cfg_att .bd .flowBox {
    margin-top: 12px;
    padding: 8px;
    background: var(--ops-overlay-03);
    border-radius: 4px;
    border-left: 2px solid var(--ops-accent);
}
/* Dim/placeholder text inside the SHARED config pane (recipe flow hint, "—" rows). Scoped to the
   container so it reads in BOTH the loop editor and the standalone crafting editor — the latter is
   outside #ops_loop_editor, where the local `.dim` color rule doesn't reach (was inheriting black). */
#ops_container .le_cfg .dim { color: var(--ops-dim); }
/* "Done" footer — the config auto-saves, so Done is the clear save-and-close primary action (× reads
   as discard). Accent-filled to stand out; hover keeps dark-on-accent instead of the default .ops_btn
   accent-on-accent (which would be invisible). */
#ops_container .le_cfg_att .bd .cj_foot { display: flex; justify-content: flex-end; margin-top: 12px; }
#ops_container .le_cfg_att .bd .cj_done { background: var(--ops-accent); color: var(--ops-bg-deep); border-color: var(--ops-accent); font-weight: 600; padding: 5px 20px; }
#ops_container .le_cfg_att .bd .cj_done:hover { background: var(--ops-accent); color: var(--ops-bg-deep); filter: brightness(1.08); }
/* Per-job flow rollup (produces / consumes per day) inside the flowBox. */
#ops_container .le_cfg_att .flowBox .fjhd {
    font-size: 11px;
    color: var(--ops-txt);
    font-weight: 600;
    margin-bottom: 2px;
}
#ops_container .le_cfg_att .flowBox .fjhd2 {
    font-size: 10px;
    color: var(--ops-dim);
    margin-bottom: 6px;
}
#ops_container .le_cfg_att .flowBox .fjsec { margin-top: 4px; }
#ops_container .le_cfg_att .flowBox .fjh {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--ops-dim);
    margin: 4px 0 2px;
}
#ops_container .le_cfg_att .flowBox .fjrow {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    font-size: 11px;
    padding: 1px 0;
}
#ops_container .le_cfg_att .flowBox .fjn { color: var(--ops-txt); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#ops_container .le_cfg_att .flowBox .fjv { font-variant-numeric: tabular-nums; white-space: nowrap; }
#ops_container .le_cfg_att .flowBox .fjv.pos { color: var(--ops-green); }
#ops_container .le_cfg_att .flowBox .fjv.neg { color: var(--ops-red); }
#ops_container .le_cfg_att .flowBox .fjv .fju { color: var(--ops-dim); font-size: 9px; margin-left: 1px; }
/* Recipe search — replaces the native <select> which chokes at 3k+ options. Absolute-positioned
   dropdown so it can overlay the row2 inputs beneath without shifting layout. */
#ops_container .le_cfg_att .recipeSearch {
    position: relative;
}
#ops_container .le_cfg_att .recipeList {
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    margin-top: 2px;
    max-height: 260px;
    overflow-y: auto;
    background: var(--ops-bg-sunken);
    border: 1px solid var(--ops-line);
    border-radius: 4px;
    box-shadow: 0 6px 18px var(--ops-shadow-55);
    z-index: 10;
}
#ops_container .le_cfg_att .recipeList .opt {
    padding: 4px 8px;
    font-size: 12px;
    cursor: pointer;
    color: var(--ops-txt);
}
#ops_container .le_cfg_att .recipeList .opt:hover { background: var(--ops-overlay-05); }
#ops_container .le_cfg_att .recipeList .opt.sel { background: var(--ops-sel-12); color: var(--ops-accent); }
#ops_container .le_cfg_att .recipeList .empty,
#ops_container .le_cfg_att .recipeList .more {
    padding: 5px 8px;
    font-size: 10px;
    color: var(--ops-dim);
    font-style: italic;
    border-top: 1px solid var(--ops-line);
}
#ops_container .le_cfg_att .recipeList .empty { font-style: italic; }
/* Type badges on unified picker rows (ship config / building) + the contextual sub-dropdown row. */
#ops_container .le_cfg_att .recipeList .opt .cj_badge {
    float: right;
    margin-left: 8px;
    padding: 0 6px;
    border-radius: 8px;
    font-size: 9px;
    line-height: 15px;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--ops-bg-deep);
    background: var(--ops-dim);
}
#ops_container .le_cfg_att .recipeList .opt .cj_badge_cfg { background: var(--ops-accent); }
#ops_container .le_cfg_att .recipeList .opt .cj_badge_bld { background: var(--ops-type-asteroid); }
#ops_container .le_cfg_att .bd .cj_variant:empty { display: none; }
#ops_container .le_cfg_att .bd .cj_variant { margin-top: 2px; }
/* Grouped browse-view headers (empty search): Ship Configs / Buildings / Recipes. */
#ops_container .le_cfg_att .recipeList .cj_grouphd {
    padding: 5px 8px 2px;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--ops-dim);
    border-top: 1px solid var(--ops-line);
}
#ops_container .le_cfg_att .recipeList .cj_grouphd:first-child { border-top: none; }
#ops_container .le_cfg_att .recipeList .cj_grouphd .cj_grouphd_more { text-transform: none; letter-spacing: 0; opacity: .75; }

/* ---- per-leg movement arrowheads on the ring ---- */
#ops_loop_editor .le_mv {
    position: absolute;
    width: 17px;
    height: 17px;
    transform: translate(-50%, -50%);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}
#ops_loop_editor .le_mv svg { display: block; overflow: visible; }
#ops_loop_editor .le_mv.opti_time { color: var(--ops-type-base-lt); }
#ops_loop_editor .le_mv.opti_fuel { color: var(--ops-type-asteroid); }
#ops_loop_editor .le_mv:not(.set) { color: var(--ops-muted); opacity: .7; }
#ops_loop_editor .le_mv:hover { color: var(--ops-accent); }

/* ---- selected-stop config card (in the centre of the ring) ---- */
#ops_container .le_cfg {
    position: absolute;
    width: 280px;
    transform: translate(-50%, -50%);
    background: var(--ops-panel3);
    border: 1px solid var(--ops-accent);
    border-radius: 11px;
    box-shadow: 0 10px 30px var(--ops-shadow-60);
    overflow: hidden;
    z-index: 5;
}
#ops_container .le_cfg .ch {
    padding: 8px 10px;
    border-bottom: 1px solid var(--ops-line);
    display: flex;
    align-items: center;
    gap: 6px;
}
#ops_container .le_cfg .ch .dot { width: 8px; height: 8px; border-radius: 50%; }
#ops_container .le_cfg .ch .ty {
    font-size: 9px;
    text-transform: uppercase;
    color: var(--ops-dim);
    padding: 1px 6px;
    border-radius: 7px;
    background: var(--ops-panel2);
}
#ops_container .le_cfg .cb { padding: 8px 10px; }
#ops_container .le_cfg label { color: var(--ops-dim); font-size: 10px; display: block; margin-top: 4px; }
#ops_container .le_cfg input { width: 100%; background: var(--ops-bg-deep); }
#ops_container .le_cfg .coord { color: var(--ops-dim); font-size: 10px; margin: 3px 0 2px; }
#ops_container .le_cfg .entpick { position: relative; }
#ops_container .le_cfg .entres {
    max-height: 120px;
    overflow: auto;
    border: 1px solid var(--ops-line);
    border-radius: 4px;
    margin-top: 3px;
    display: none;
}
#ops_container .le_cfg .entpick:focus-within .entres { display: block; }
#ops_container .le_cfg .er { padding: 3px 7px; cursor: pointer; font-size: 11px; }
#ops_container .le_cfg .er:hover { background: var(--ops-line); color: var(--ops-accent); }

#ops_container .le_cfg .act {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 7px;
    margin: 4px 0;
    background: var(--ops-bg-sunken);
    border: 1px solid var(--ops-line);
    border-radius: 6px;
}
#ops_container .le_cfg .act .l { flex: 1; }
#ops_container .le_cfg .act .x,
#ops_container .le_cfg .res .x { color: var(--ops-dim); cursor: pointer; }
#ops_container .le_cfg .act .x:hover,
#ops_container .le_cfg .res .x:hover { color: var(--ops-red); }
/* Claim-stake transfer config: a Conditional checkbox + hint. Reset the checkbox out of the generic
   input styling; hint is small + dim. */
#ops_container .le_cfg .le_xfer { padding: 6px 8px; }
#ops_container .le_cfg .le_cond { display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 11px; color: var(--ops-txt); }
#ops_container .le_cfg .le_cond input[type="checkbox"] { width: auto; min-width: 0; margin: 0; padding: 0; background: none; border: 0; cursor: pointer; accent-color: var(--ops-accent); }
#ops_container .le_cfg .le_cond_hint { margin-top: 3px; font-size: 10px; line-height: 1.35; }
/* D — same-fleet conditional claimstake watchdog badge, a ONE-LINE box beside the base HEADLINE, reached by
   a little arrow (mirrors the crafting '+' adder gaps). Border matches the starbase card (--ops-node-line),
   not green. See project_c4_loop_claimstake_gating. */
#ops_loop_editor .le_wdarrow {
    position: absolute;
    transform: translate(-50%, -50%);
    font-size: 14px;
    line-height: 1;
    color: var(--ops-dim);
    z-index: 2;
    pointer-events: none;
}
#ops_loop_editor .le_wdbadge {
    position: absolute;
    transform: translateY(-50%);
    padding: 4px 9px;
    font-size: 10px;
    line-height: 1.3;
    white-space: nowrap;
    color: var(--ops-type-cs);
    background: var(--ops-node-bg);
    border: 1px solid var(--ops-node-line);
    border-radius: 8px;
    box-shadow: 0 4px 14px var(--ops-shadow-45);
    z-index: 2;
}
#ops_loop_editor .le_wdbadge b { color: var(--ops-type-cs); font-weight: 600; }
#ops_loop_editor .le_wdbadge .wdr { color: var(--ops-green); font-weight: 600; }

#ops_container .le_cfg .reswrap {
    margin: 1px 0 6px 7px;
    padding-left: 7px;
    border-left: 1px solid var(--ops-line);
}
#ops_container .le_cfg .reshd {
    display: flex;
    justify-content: space-between;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: .4px;
    color: var(--ops-dim);
    padding: 1px 0 2px;
    border-bottom: 1px solid var(--ops-line-soft);
    margin-bottom: 2px;
}
#ops_container .le_cfg .reshd span {
    font-size: 9px;
    text-transform: none;
    color: var(--ops-dim);
    font-weight: 400;
}
#ops_container .le_cfg .res {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 2px 0;
}
/* Cargo-category locked — same rule as .le_menu div.locked above but for the SELECTED-resource
   card inside a mine action. .rn is the resource name span (defined below at line ~511); we
   recolor it red. Change this one line to re-tune. See _c4_isCargoLocked() for the check. */
#ops_container .le_cfg .res.locked .rn { color: var(--ops-red); }
#ops_container .le_cfg .res .rn {
    flex: 1;
    min-width: 0;
    font-size: 11px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#ops_container .le_cfg .res .ry {
    color: var(--ops-green);
    font-variant-numeric: tabular-nums;
    font-size: 11px;
}
/* Per-line over-capacity indicator (amber ⚠ + surviving amount), sits just left of the qty input. */
#ops_container .le_cfg .res .le_trim_warn {
    flex: 0 0 auto;
    color: var(--ops-warn);
    font-size: 10px;
    font-weight: 600;
    white-space: nowrap;
    cursor: help;
    font-variant-numeric: tabular-nums;
}
#ops_container .le_cfg .res .ca {
    flex: 0 0 54px;
    width: 54px;
    margin: 0;
    padding: 2px 4px;
    font-size: 11px;
    text-align: right;
}

/* ---- point-stop X/Y picker + use-fleet-loc / select-on-map buttons ---- */
#ops_container .le_cfg .le_xy { display: flex; align-items: center; gap: 4px; margin-top: 3px; }
#ops_container .le_cfg .le_xy .cl { color: var(--ops-type-asteroid); font-size: 11px; font-weight: 600; }
#ops_container .le_cfg .le_xy input {
    flex: 1;
    background: var(--ops-bg-deep);
    color: var(--ops-type-asteroid);
    padding: 3px 5px;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}
#ops_container .le_cfg .le_xy_btns { display: flex; gap: 6px; margin-top: 5px; }
#ops_container .le_cfg .le_btn {
    flex: 1;
    padding: 3px 6px;
    border: 1px solid var(--ops-line);
    border-radius: 4px;
    color: var(--ops-dim);
    font-size: 10px;
    text-align: center;
    cursor: pointer;
    background: var(--ops-bg-sunken);
}
#ops_container .le_cfg .le_btn:hover { border-color: var(--ops-accent); color: var(--ops-accent); }
/* Reorder controls: two compact square arrows on the right of the Delete-stop row. */
#ops_container .le_cfg .le_reorder { margin-left: auto; display: flex; gap: 6px; }
#ops_container .le_cfg .le_btn.le_move { flex: 0 0 auto; width: 28px; padding: 3px 0; }
#ops_container .le_cfg .le_btn.le_dis { opacity: 0.35; pointer-events: none; }

/* Starbase selector injected at the top of the crafting-job config (New-job flow). */
#ops_container .le_cfg .ce_sbpick { margin-bottom: 8px; }
#ops_container .le_cfg .ce_sbpick select {
    width: 100%;
    box-sizing: border-box;
    margin-top: 2px;
    padding: 4px 6px;
    font-size: 12px;
    background: var(--ops-bg-deep);
    color: var(--ops-txt);
    border: 1px solid var(--ops-line);
    border-radius: 4px;
}
#ops_container .le_cfg .ce_sbpick select:focus { border-color: var(--ops-accent); outline: none; }

/* ---- scan pattern picker + prediction + kit slider ---- */
#ops_container .le_cfg .le_pat {
    width: 100%;
    background: var(--ops-bg-deep);
    color: var(--ops-txt);
    border: 1px solid var(--ops-line);
    border-radius: 4px;
    padding: 3px 5px;
    font-size: 11px;
    margin-top: 2px;
}
#ops_container .le_cfg .le_pred {
    font-size: 11px;
    font-weight: 600;
    margin-top: 4px;
    text-align: right;
}
#ops_container .le_cfg .le_kits {
    width: 100%;
    margin-top: 2px;
    accent-color: var(--ops-type-asteroid);
}
#ops_container .le_cfg .le_kits_lab {
    font-size: 11px;
    color: var(--ops-txt);
    margin-top: 2px;
    font-variant-numeric: tabular-nums;
}
#ops_container .le_cfg .le_kits_lab b { color: var(--ops-accent); }
#ops_container .le_cfg .le_kits_lab .dim { color: var(--ops-dim); font-size: 10px; }
#ops_container .le_cfg .le_kits_lab.over { color: var(--ops-red); }
#ops_container .le_cfg .le_kits_lab.over b { color: var(--ops-red); }

#ops_container .le_cfg .addres,
#ops_container .le_cfg .addact {
    width: 100%;
    border-style: dashed;
    color: var(--ops-dim);
    font-size: 11px;
    padding: 3px;
    margin-top: 4px;
}
#ops_container .le_cfg .cfgfoot { margin-top: 6px; display: flex; align-items: center; gap: 6px; }

/* ---- floating menus / popups (outside #ops_loop_editor in the DOM) ---- */
.le_menu {
    position: absolute; /* positioned via _placePopAtMouse() — see .le_mvpop note */
    background: var(--ops-panel2);
    border: 1px solid var(--ops-line);
    border-radius: 6px;
    z-index: 9999;
    padding: 4px;
    font: 13px EEMfontLight, Helvetica, Arial, sans-serif;
    max-height: 320px;
    overflow: auto;
}
.le_menu div {
    padding: 4px 12px;
    cursor: pointer;
    border-radius: 4px;
    white-space: nowrap;
    color: var(--ops-txt);
}
.le_menu div:hover { background: var(--ops-line); color: var(--ops-accent); }
/* Cargo-category locked — resource can't be mined because the character hasn't unlocked its
   cargo category. Selection still works (user may plan to unlock research); the color + title
   convey the risk. See js/ops_loop_editor_c4.js _c4_isCargoLocked() for the rule. Change these
   two colors to re-tune the look; hover keeps the accent for affordance. */
.le_menu div.locked { color: var(--ops-red); }
.le_menu div.locked:hover { background: var(--ops-red-dim-bg); color: var(--ops-red); }
/* Searchable menu (auto for long lists — asteroid resources / full cargo). Sticks at the top while the
   rows scroll under it. Mirrors the sunken input look used elsewhere (ml_sel / mnt_sel). */
.le_menu_search {
    position: sticky; top: -4px;
    display: block; width: 100%; box-sizing: border-box;
    margin: -4px 0 4px; padding: 4px 8px;
    background: var(--ops-bg-sunken); border: 1px solid var(--ops-line); border-radius: 4px;
    color: var(--ops-txt); font: 12px EEMfontLight, Helvetica, Arial, sans-serif; outline: none;
}
.le_menu_search:focus { border-color: var(--ops-accent); }
.le_menu_search::placeholder { color: var(--ops-txt); opacity: .45; }

.le_mvpop {
    /* Mounted inside #ops_container (for --ops-* token inheritance), which lives inside the
       positioned #overlay_container. JS positions it via _placePopAtMouse() — clientX/Y minus the
       offsetParent rect — so it lands at the mouse regardless of that wrapper offset. */
    position: absolute;
    z-index: 9999;
    width: 200px;
    background: var(--ops-panel3);
    border: 1px solid var(--ops-accent);
    border-radius: 9px;
    box-shadow: 0 10px 30px var(--ops-shadow-60);
    padding: 9px 11px;
    font: 13px EEMfontLight, Helvetica, Arial, sans-serif;
    color: var(--ops-txt);
}
.le_mvpop .mh { font-weight: 600; margin-bottom: 4px; }
.le_mvpop label { color: var(--ops-dim); font-size: 10px; display: block; margin: 6px 0 3px; }
.le_mvpop .seg { display: flex; gap: 5px; }
.le_mvpop .opt {
    flex: 1;
    text-align: center;
    padding: 5px 4px;
    border: 1px solid var(--ops-line);
    border-radius: 5px;
    cursor: pointer;
    font-size: 11px;
    color: var(--ops-txt);
}
.le_mvpop .opt.on {
    border-color: var(--ops-accent);
    color: var(--ops-accent);
    background: var(--ops-accent-08);
}
.le_mvpop .chk {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--ops-txt);
    font-size: 11px;
    margin-top: 9px;
}
.le_mvpop .chk input { width: auto; margin: 0; }
.le_mvpop .chk .sub { color: var(--ops-dim); }
.le_mvpop .mf { margin-top: 10px; text-align: right; }
.le_mvpop button {
    background: var(--ops-panel2);
    color: var(--ops-txt);
    border: 1px solid var(--ops-line);
    border-radius: 4px;
    padding: 4px 14px;
    cursor: pointer;
    font: inherit;
}
.le_mvpop button:hover { border-color: var(--ops-accent); color: var(--ops-accent); }

/* ==== from ops_flows.css (material-flows view) ==== */
/* C4 OPERATIONS — SHARED MATERIAL-FLOWS VIEW (Phase 3, 2026-07-10)
 * Host is #ops_flows_view — a flex-fill panel shown only when the overview View toggle is "Flows"
 * (JS flips display:flex; opsLoopMode drops container_shrink so it takes full height, like #ops_schema).
 * DESIGN TOKENS live in css/ops_shared.css on #ops_container scope — do NOT redefine here.
 * SITE RULE: no hardcoded color literals; every color is a var(--ops-*). */

#ops_flows_view {
    display: none;              /* opsSetViewMode('flows') flips to flex */
    flex: 1 1 0;
    min-height: 0;
    flex-direction: column;
    overflow: hidden;
    position: relative;
    background: var(--ops-panel);
    color: var(--ops-txt);
    font: 400 12px EEMfontLight, Helvetica, Arial, sans-serif;
}
#ops_flows_view * { box-sizing: border-box; }

/* ---- top bar: Simulation/Live toggle (the bar + title now use shared .ops_bar / .ops_title) ---- */
.ofl_modes {
    margin-left: auto;
    display: inline-flex;
    border: 1px solid var(--ops-line);
    border-radius: 4px;
    overflow: hidden;
}
.ofl_mode {
    padding: 4px 12px;
    cursor: pointer;
    color: var(--ops-dim);
    background: var(--ops-panel2);
    font-size: 11px;
    user-select: none;
}
.ofl_mode + .ofl_mode { border-left: 1px solid var(--ops-line); }
.ofl_mode:hover { background: var(--ops-panel2-hi); color: var(--ops-txt); }
.ofl_mode.on { background: var(--ops-accent); color: var(--ops-ink); }
/* Phase D actual-mode window picker (24h / 7d / 30d) — mirrors the mode toggle. */
.ofl_win { display: inline-flex; margin-left: 8px; border: 1px solid var(--ops-line); border-radius: 4px; overflow: hidden; }
.ofl_winopt { padding: 4px 10px; cursor: pointer; color: var(--ops-dim); background: var(--ops-panel2); font-size: 11px; user-select: none; }
.ofl_winopt + .ofl_winopt { border-left: 1px solid var(--ops-line); }
.ofl_winopt:hover { background: var(--ops-panel2-hi); color: var(--ops-txt); }
.ofl_winopt.on { background: var(--ops-accent); color: var(--ops-ink); }
/* Actual-mode aggregation picker (By starbase / By operation) — mirrors the window picker. */
.ofl_aggopt { padding: 4px 10px; cursor: pointer; color: var(--ops-dim); background: var(--ops-panel2); font-size: 11px; user-select: none; }
.ofl_aggopt + .ofl_aggopt { border-left: 1px solid var(--ops-line); }
.ofl_aggopt:hover { background: var(--ops-panel2-hi); color: var(--ops-txt); }
.ofl_aggopt.on { background: var(--ops-accent); color: var(--ops-ink); }
/* By-operation category filter chips (multi-toggle) — off = dim, on = accent. */
.ofl_catopt { padding: 4px 9px; cursor: pointer; color: var(--ops-dim); background: var(--ops-panel2); font-size: 11px; user-select: none; opacity: .5; }
.ofl_catopt + .ofl_catopt { border-left: 1px solid var(--ops-line); }
.ofl_catopt:hover { background: var(--ops-panel2-hi); color: var(--ops-txt); }
.ofl_catopt.on { background: var(--ops-accent); color: var(--ops-ink); opacity: 1; }

/* ---- Statistics dashboard (ops_stats_view_c4.js) ---- */
#ops_stats_view { flex-direction: column; min-height: 0; }
#ops_stats_view.ops_view { flex: 1 1 0; }
/* Tile layout: cards WRAP so this becomes a configurable dashboard grid later. Each card ~30% wide. */
.stats_body { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 12px; background: var(--ops-panel);
    display: flex; flex-wrap: wrap; align-content: flex-start; gap: 12px; }
.stats_card { flex: 0 0 30%; min-width: 340px; max-width: 560px; box-sizing: border-box; align-self: flex-start;
    background: var(--ops-panel2); border: 1px solid var(--ops-line); border-radius: 8px; padding: 10px 12px 8px; }
.stats_chart { width: 100%; height: 190px; }
.stats_note { flex: 1 1 100%; }
.stats_cardh { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; flex-wrap: wrap; }
.stats_cardt { font: 600 12px EEMfont, Helvetica, Arial, sans-serif; letter-spacing: .04em; color: var(--ops-txt); }
.stats_tot { margin-left: auto; font-size: 11px; color: var(--ops-txt); white-space: nowrap; }
/* Actual-mode Expected/Observed cells carry no sign class → force a readable colour (Δ keeps pos/neg). */
.ofl_tbl .ofl_amt.ofl_val { color: var(--ops-txt); }
/* Per-operation "Costs" separator row inside an op card. */
.ofl_tbl tr.ofl_costsep td { padding-top: 6px; border-top: 1px solid var(--ops-line); font-size: 9px; letter-spacing: .08em; text-transform: uppercase; }
/* Reset observed-ledger button (danger-tinted, tokens only). */
.ofl_reset { padding: 5px 12px; font-size: 11px; cursor: pointer; border-radius: 4px; user-select: none;
    color: var(--ops-red); background: var(--ops-panel2);
    border: 1px solid color-mix(in srgb, var(--ops-red) 45%, transparent); }
.ofl_reset:hover { background: color-mix(in srgb, var(--ops-red) 16%, var(--ops-panel2)); }

/* ---- 3-column body ---- */
.ofl_body {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    gap: 1px;
    background: var(--ops-line-soft);   /* shows as thin dividers between columns */
}
.ofl_col {
    flex: 1 1 0;
    min-width: 0;
    overflow-y: auto;
    padding: 10px 12px;
    background: var(--ops-panel);
}
.ofl_sources { flex: 0 0 230px; background: var(--ops-panel3); }
.ofl_totals  { flex: 0 0 240px; background: var(--ops-panel3); }
.ofl_colh {
    font: 600 10px EEMfont, Helvetica, Arial, sans-serif;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--ops-dim2);
    padding-bottom: 8px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--ops-line-soft);
}
.ofl_empty {
    color: var(--ops-dim2);
    font-style: italic;
    padding: 4px 0;
    font-size: 11px;
}

/* ---- per-starbase ledger cards (middle column) ---- */
.ofl_sb {
    background: var(--ops-card);
    border: 1px solid var(--ops-line-soft);
    border-radius: 5px;
    padding: 8px 10px;
    margin-bottom: 10px;
}
.ofl_sbh {
    font: 600 12px EEMfont, Helvetica, Arial, sans-serif;
    color: var(--ops-txt-hi);
    padding-bottom: 6px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--ops-line-soft);
}
.ofl_sbmeta {
    float: right;
    font: 400 10px EEMfontLight, Helvetica, Arial, sans-serif;
    color: var(--ops-dim2);
    letter-spacing: 0;
}
/* two side-by-side tables per starbase: Actions/Day (left) + Item Totals/Day (right) */
.ofl_sbtables { display: flex; gap: 12px; align-items: flex-start; }
.ofl_tbl { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 11.5px; }
.ofl_actions { flex: 1 1 62%; }
.ofl_itotals { flex: 1 1 38%; }
.ofl_tbl th {
    text-align: left;
    color: var(--ops-dim2);
    font: 600 9.5px EEMfont, Helvetica, Arial, sans-serif;
    letter-spacing: .06em;
    text-transform: uppercase;
    padding: 0 6px 4px 0;
    border-bottom: 1px solid var(--ops-line-soft);
    white-space: nowrap;
}
.ofl_tbl td { padding: 2px 6px 2px 0; vertical-align: baseline; }
.ofl_tbl tbody tr:hover td { background: var(--ops-overlay-03); }
.ofl_tbl .ofl_dir { width: 12px; text-align: center; padding-right: 4px; font-size: 8px; }
.ofl_tbl .ofl_dir.pos { color: var(--ops-green); }
.ofl_tbl .ofl_dir.neg { color: var(--ops-red); }
.ofl_tbl .ofl_act { width: 42px; color: var(--ops-dim); }
.ofl_tbl td.ofl_n {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--ops-txt); max-width: 0;   /* fixed-layout ellipsis */
}
.ofl_tbl .ofl_amt { text-align: right; width: 68px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ofl_tbl .ofl_amt.pos  { color: var(--ops-green); }
.ofl_tbl .ofl_amt.neg  { color: var(--ops-red); }
.ofl_tbl .ofl_amt.zero { color: var(--ops-dim2); }
.ofl_tbl .ofl_empty { padding: 4px 0; }

/* shared cell/text bits reused by the grand-totals column */
.ofl_n { color: var(--ops-txt); }
.ofl_net  { text-align: right; font-variant-numeric: tabular-nums; padding-left: 8px; }
.ofl_net.pos  { color: var(--ops-green); }
.ofl_net.neg  { color: var(--ops-red); }
.ofl_net.zero { color: var(--ops-dim2); }

/* transactions + zink fee footer under the grand totals */
.ofl_fees {
    margin-top: 12px;
    padding-top: 8px;
    border-top: 1px solid var(--ops-line);
}
.ofl_feerow {
    display: flex;
    justify-content: space-between;
    padding: 2px 0;
    font-size: 11.5px;
    color: var(--ops-dim);
}
.ofl_feerow span:last-child { color: var(--ops-txt); font-variant-numeric: tabular-nums; }

/* ---- net totals (right column) ---- */
.ofl_trow {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: 3px 0;
    border-bottom: 1px solid var(--ops-line-soft);
    font-size: 12px;
}
.ofl_trow .ofl_n { flex: 1 1 auto; }
.ofl_trow .ofl_net { flex: 0 0 auto; font-size: 13px; }
.ofl_u { color: var(--ops-dim2); font-size: 9px; margin-left: 2px; }

/* ---- sources (left column: include-in-calc toggles) ---- */
.ofl_srcgrp { margin-bottom: 14px; }
.ofl_srch {
    font: 600 10px EEMfont, Helvetica, Arial, sans-serif;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ops-dim);
    margin-bottom: 4px;
}
.ofl_srcsb {
    color: var(--ops-dim2);
    font-size: 10px;
    margin: 6px 0 2px;
    padding-left: 2px;
}
.ofl_src {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 4px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 11.5px;
}
.ofl_src:hover { background: var(--ops-overlay-04); }
.ofl_src.dim { opacity: .45; }
.ofl_src input { accent-color: var(--ops-accent); cursor: pointer; margin: 0; }
.ofl_srcn {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--ops-txt);
}
.ofl_srcf { color: var(--ops-dim2); font-size: 10px; flex: 0 0 auto; }
.ofl_dim  { color: var(--ops-dim2); font-style: italic; }
.ofl_run {
    flex: 0 0 auto;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--ops-green);
    box-shadow: 0 0 5px var(--ops-green);
}

/* ==== crafting windows (was ops_crafting_editor.css / #ops_crafting_editor — now class-based) ==== */
/* C4 CRAFTING EDITOR — per-starbase crafting-jobs view (ops_crafting_editor_c4.js). Host = .ops_view.
   Class-based (no id scoping); --ops-* tokens inherit from #ops_container. Tokens only — no literals. */
.ce_root { display: flex; flex-direction: column; height: 100%; min-height: 0; color: var(--ops-txt); }

/* empty state */
.ce_empty { padding: 28px 16px; color: var(--ops-dim); font-size: 0.95rem; line-height: 1.5; text-align: center; }

/* windows grid */
.ce_wrap {
    display: flex; flex-wrap: wrap; align-content: flex-start; gap: 12px;
    padding: 12px; overflow-y: auto; min-height: 0;
}

/* one window per starbase — CHROME (box + header + title) comes from the shared .ops_node /
   .ops_node_head / .ops_node_title (applied in ops_crafting_editor_c4.js). Only view-specific
   LAYOUT stays here. */
.ce_sb { width: 300px; display: flex; flex-direction: column; }
/* Transient highlight when the schema view's craft-node click focuses this starbase window. */
.ce_sb_focus {
    border-color: var(--ops-accent);
    animation: ce_focus_pulse 1.4s ease-out 1;
}
@keyframes ce_focus_pulse {
    0%   { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ops-accent) 80%, transparent); }
    100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--ops-accent) 0%, transparent); }
}

/* job rows — row CHROME from shared .ops_node_row; the cells (name/qty/×) from shared .ops_row_*;
   the running dot from shared .ops_dot*. Only the crafting-specific bits remain here. */
.ce_jobs { display: flex; flex-direction: column; }
.ce_job_btns { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
.ce_job_empty { padding: 8px; font-size: 10px; }
/* Compact two-column job row: recipe name STACKED over the amount·crew·runs meta on the left, the
   buttons floated right and vertically centered across BOTH text lines (rowspan-style) via the row's
   shared align-items:center. Frees the name from the qty + buttons that were truncating it. The name
   keeps its own ellipsis inside the (min-width:0) left column. Scoped to .ce_sb so the shared
   .ops_node_row (maintainer editor) keeps its single-line layout. */
.ce_sb .ce_row_main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; justify-content: center; }
.ce_sb .ce_row_main .ops_row_name { flex: 0 0 auto; }
.ce_sb .ce_row_main .ops_row_qty { margin-top: 1px; }
/* FROM RAW per-row toggle — its own compact column between the name stack and the buttons. */
.ce_sb .ce_fromraw { flex: 0 0 auto; display: flex; align-items: center; gap: 4px; cursor: pointer; font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--ops-dim); white-space: nowrap; }
.ce_sb .ce_fromraw input { margin: 0; cursor: pointer; }
/* research-gate warning: recipe needs a research unlock the character lacks (createCraft would
   chain-reject 0x51890014 "Requirements not met"). Warn colour; the name (flex 1 1 auto) ellipsis-
   shrinks to make room. Full text via the row's title attr. */
.ce_locked { flex: 0 0 auto; max-width: 45%; color: var(--ops-warn); font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.ce_addjob { padding: 7px 10px; border-top: 1px solid var(--ops-node-line); }

/* flows section — CHROME comes from the shared .ops_node_flows / .ops_node_flows_hd (matches the
   loop editor's per-stop flow summary). */


/* ---- config-pane overlay (edit / new) ---- */
.ce_overlay {
    position: fixed; inset: 0; z-index: 100000;
    display: flex; align-items: center; justify-content: center;
    background: color-mix(in srgb, var(--ops-shadow) 55%, transparent);
}
/* the reused loop-editor config pane (.le_cfg) is absolute in the loop stage; inside the overlay we
   place it in normal flow, centered. */
.ce_overlay .le_cfg { position: relative !important; left: auto !important; top: auto !important; }
.ce_newwrap {
    background: var(--ops-panel); border: 1px solid var(--ops-line); border-radius: 10px;
    padding: 12px; box-shadow: 0 10px 30px color-mix(in srgb, var(--ops-shadow) 50%, transparent);
    display: flex; flex-direction: column; gap: 10px; max-width: 92vw;
}
.ce_newsel { display: flex; align-items: center; gap: 8px; }
.ce_newsel label { color: var(--ops-dim); font-size: 0.85rem; }
.ce_newsel select {
    flex: 1 1 auto; background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 6px; padding: 5px 8px; font-size: 0.9rem;
}
.ce_new_cfg { min-width: 300px; }
.ce_new_hint { padding: 18px 8px; text-align: center; font-size: 0.9rem; color: var(--ops-dim); }

/* ---- CLAIM-STAKE MAINTAINER EDITOR (ops_maintainer_editor_c4.js) — one window per system. CHROME
   (box/header/title/rows) comes from the shared .ops_node / .ops_node_head / .ops_node_title /
   .ops_node_row (applied in JS). Only view LAYOUT + the labelled control stack live here. Tokens only,
   no literals — mirrors the crafting editor. See project_c4_claimstake_maintainer. ---- */
/* mnt_root / mnt_wrap MIRROR ce_root / ce_wrap so the maintenance + crafting panes look identical:
   the node breathing room + scroll live on the WRAP (padding + overflow-y), NOT the root. */
.mnt_root { display: flex; flex-direction: column; height: 100%; min-height: 0; color: var(--ops-txt); }
.mnt_wrap { display: flex; flex-wrap: wrap; align-content: flex-start; gap: 12px; padding: 12px; overflow-y: auto; min-height: 0; }
.mnt_sys { width: 300px; display: flex; flex-direction: column; }
.mnt_sys > .ops_node_head { justify-content: space-between; padding: 7px 10px; }
/* Fleet + Drop-off stack ONE UNDER THE OTHER (block-labelled fields, like the loop-editor config pane). */
.mnt_ctrls { display: flex; flex-direction: column; gap: 7px; padding: 8px 10px; border-top: 1px solid var(--ops-node-line); }
.mnt_field { display: flex; flex-direction: column; gap: 2px; }
.mnt_field > .mnt_lbl { color: var(--ops-dim); font-size: 10px; }
#ops_maintainer_editor select.mnt_sel {
    width: 100%; max-width: 100%; box-sizing: border-box;
    background: var(--ops-bg-sunken); color: var(--ops-txt);
    border: 1px solid var(--ops-line); border-radius: 4px;
    padding: 3px 6px; font: 500 11px EEMfont, Helvetica, Arial, sans-serif; cursor: pointer;
}
#ops_maintainer_editor select.mnt_sel:focus { outline: none; border-color: var(--ops-accent); }
.mnt_stakes { display: flex; flex-direction: column; border-top: 1px solid var(--ops-node-line); }
/* Per-stake row = TEXT (name + type + status) on the LEFT, On/Off toggle FLOATED RIGHT. Collect toggle
   removed 2026-07-24 (auto net-positive collect). See project_c4_claimstake_maintainer. */
.mnt_stake { display: flex; flex-direction: row; align-items: center; justify-content: space-between; gap: 8px; padding: 5px 10px; border-top: 1px solid var(--ops-node-line); }
.mnt_stake:first-child { border-top: none; }
.mnt_stake_txt { flex: 1 1 auto; min-width: 0; overflow: hidden; }
.mnt_stake_name { color: var(--ops-txt); font-size: 12px; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mnt_stake_ctl { flex: 0 0 auto; }
.mnt_stake_excl { opacity: 0.55; }
.mnt_warn { padding: 4px 10px 0; }
.mnt_empty { padding: 14px; }

/* ==== from ops_overview.css (schema view only; old .ov_* context-header rules retired) ==== */
/* ---- Schema view: force-directed abstract graph.
   Host is #ops_schema — a flex-fill container that shows only when the View toggle is Schema
   (JS sets display:flex in that mode; opsLoopMode also drops the container_shrink class so it
   takes full screen height, matching the loop editor). */
#ops_schema {
    display: none; /* opsSetViewMode('schema') flips to flex */
    flex: 1 1 0;
    min-height: 0;
    /*background: var(--ops-panel);
    border-top: 1px solid var(--ops-line);*/
    overflow: hidden;
    position: relative;
}
.ov_schema_svg {
    display: block;
    width: 100%;
    height: 100%;
    cursor: grab;
    /*background-color: rgba(0,0,0,0.5);
    backdrop-filter: blur(30px);*/
}
.ov_schema_svg:active {
    cursor: grabbing;
}
.ov_schema_node circle {
    fill: var(--ops-node-bg);          /* loop-graphics node body (shared with loop editor stops) */
    stroke: var(--ops-txt);            /* bright outline is a schema-specific choice, kept as-is */
    stroke-width: 1.5;
}
/* Asteroids: dashed circle border — signals a mining site vs a fixed station. */
.ov_schema_node_asteroid circle {
    stroke-dasharray: 4 3;
}
/* Point-in-space stops: no border — they're waypoints, not physical entities. */
.ov_schema_node_point circle {
    stroke: none;
}
/* Starbases with configured crafting jobs: accent ring + a job-count badge (top-right). Clicking
   the node opens the dedicated crafting editor at that starbase (ops_schema_view_c4.js). Double
   class beats the base `.ov_schema_node circle` rule regardless of source order. */
.ov_schema_node.ov_schema_node_craft { cursor: pointer; }
.ov_schema_node.ov_schema_node_craft circle {
    stroke: var(--ops-accent);
    stroke-width: 2.25;
}
.ov_schema_craft_badge {
    fill: var(--ops-accent);
    stroke: var(--ops-bg);
    stroke-width: 1.5;
}
.ov_schema_craft_badge_txt {
    fill: var(--ops-bg);
    font: 700 11px EEMfont, Helvetica, Arial, sans-serif;
    pointer-events: none;
}
.ov_schema_node .ov_schema_node_lbl {
    fill: var(--ops-txt);
    font: 600 12px EEMfont, Helvetica, Arial, sans-serif;
    pointer-events: none;
}
.ov_schema_edge {
    stroke-width: 1.44;
    transition: stroke-opacity .12s ease;
}
.ov_schema_arrow {
    transition: fill-opacity .12s ease;
}
.ov_schema_lbl {
    font: 500 10px EEMfontLight, Helvetica, Arial, sans-serif;
    pointer-events: none;
    transition: fill-opacity .12s ease;
    paint-order: stroke;
    stroke: var(--ops-bg);
    stroke-width: 2;
}

/* ---- container collapse in overview mode ----
   Overview only takes the space its 2 rows need; the surrounding wrapper + #ops_container drop
   to auto-height so the map can show through below. Reverted when in loop-editor mode via the
   opsLoopMode class toggle.
   The `> div` INLINE style="height:100%" in index.html has higher specificity than a class
   selector, so `!important` is required to beat it. When we drop the class (loop mode), the
   inline height:100% takes over again and the container fills the screen. */
#ops_container.ops_container_shrink,
#ops_container.ops_container_shrink > div {
    height: auto !important;
    min-height: 0 !important;
}
/* Parent .dataOverlayContainer is `pointer-events: all !important` (basic.css:220) — full-height
   even when our ops_container collapses to 98px. That empty space below the overview would
   swallow clicks meant for the game map. Using :has() to toggle it off ONLY when this ops
   instance is in shrink mode; ops_container itself re-enables pointer-events for its own
   content so tabs + buttons stay clickable. Chrome supports :has() since 105. */
#overlay_container:has(#ops_container.ops_container_shrink) {
    pointer-events: none !important;
}
#ops_container.ops_container_shrink {
    pointer-events: auto;
}

/* ==== claim-stake buildings editor (buildings_editor_c4.js) — LAYOUT glue only ====================
   #building_container is a .dataOverlay in #overlay_container (index.html) — its toprow (title + close X)
   + show/hide come from the app overlay system (opened via toggleOverlay(undefined,'building',…), like
   ops/research/crafting). The module renders ONLY into #building_content. CHROME + typography come from
   the shared primitives (.ops_node / .ops_btn / .ops_dot / .ops_label / .ops_meter / .ops_num); these
   .stkb_* rules own ONLY position / grid / size + the per-category tint. Layout inside #building_content:
   identity line · horizontal info band · config [ caps | CS core | wide-category rows ].
   (See feedback_css_consolidate_not_scatter.) */
#building_container { color: var(--ops-txt); }
#building_content { background:
    radial-gradient(1200px 800px at 50% -10%, color-mix(in srgb, var(--ops-type-base) 6%, transparent), transparent 60%),
    var(--ops-bg-deep); }
#building_content * { box-sizing: border-box; }
#building_content .ops_node { border-radius: 6px; }   /* scoped override — a bit tighter than the shared 9px default */
/* match the fleet loop editor's plain node-name styling (.ops_node_body .ops_node_name) instead of the
   shared .ops_node_title primitive's bold/blue/letter-spaced default. */
#building_content .ops_node_title { color: var(--ops-txt); font-weight: 400; letter-spacing: normal; }
.stkb_root { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; }
.stkb_ident { flex: 0 0 auto; padding: 9px 12px; font-size: 13px; font-weight: 600; color: var(--ops-txt-hi);
    background: var(--ops-node-bg); border-bottom: 1px solid var(--ops-line); }
/* editable custom-name field — LOCAL STUB (see buildings_editor_c4.js customNames()); styled to sit inline
   with the surrounding ident text instead of looking like a default browser input. */
.stkb_name_input { background: transparent; border: 1px solid transparent; border-radius: 4px; color: var(--ops-txt-hi);
    font: inherit; font-weight: 600; padding: 1px 4px; width: 16ch; }
.stkb_name_input:hover { border-color: var(--ops-line); }
.stkb_name_input:focus { outline: none; border-color: var(--ops-accent); background: var(--ops-bg-sunken); }
.stkb_name_edit_ico { opacity: .8; vertical-align: -3px; margin-right: 5px; cursor: pointer; }
.stkb_name_edit_ico:hover { opacity: 1; }

/* build-cost — list style (one material per row: name left, ×qty / +refund right), matches the info band's
   .stkb_irow rows rather than wrapped chips. Lives in the side pane's top section (see .stkb_pane_top). */
.stkb_buildcost { flex: 0 0 auto; }
.stkb_buildcost .ops_label { display: block; margin-bottom: 3px; }
/* materials matched against the servicing starbase pod — a shortfall shows red have/need + a header flag */
.stkb_shortwarn { color: var(--ops-red); font-weight: 700; text-transform: none; letter-spacing: 0; margin-left: 4px; }
.stkb_irow.stkb_short span:first-child { color: var(--ops-red); }
.stkb_have { color: var(--ops-red); font-weight: 700; }

/* horizontal info band */
/* Cap the info band at 25% of the editor body (.stkb_root is height:100% of #building_content). A long
   resource list then scrolls WITHIN its column instead of shoving the config diagram down. overflow-y:hidden
   on the band keeps the labels put (only the columns scroll); each column's header is sticky so it stays
   visible while its rows scroll. */
.stkb_info { flex: 0 0 auto; display: flex; background: var(--ops-panel); border-bottom: 1px solid var(--ops-line); overflow-x: auto; overflow-y: hidden; max-height: 25%; }
.stkb_info_col { flex: 1 1 0; min-width: 210px; padding: 8px 14px; border-right: 1px solid var(--ops-line-soft); overflow-y: auto; min-height: 0; }
.stkb_info_col .ops_label { display: block; margin-bottom: 5px; position: sticky; top: 0; background: var(--ops-panel); padding-top: 2px; z-index: 1; }
.stkb_irow { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; padding: 2px 0; }
.stkb_pending { color: var(--ops-dim); font-style: italic; font-size: 11px; margin-top: 2px; }   /* deferred flow-calc caption */
.stkb_submeta { font-size: 10px; line-height: 1.3; padding: 0 0 3px; margin-top: -1px; }          /* "have X / need Y" under a missing input */

/* bottom split — config area (left, scrolls its own overflow) + the docked side pane (right). stretch (the
   flex default) keeps both at the SAME height, matching the .stkb_band invariant one level up. */
.stkb_main { flex: 1 1 auto; display: flex; min-height: 0; gap: 14px; }

/* config area + orthogonal connectors. Scrollbar suppressed — drag-to-pan is the primary way to navigate
   (like the fleet loop editor's #le_canvas), overflow:auto stays only as a scroll-wheel/touchpad fallback. */
.stkb_cfg { flex: 1 1 auto; position: relative; min-height: 0; overflow: auto; padding: 24px 22px;
    background: radial-gradient(circle at 1px 1px, var(--ops-grid-dot) 1px, transparent 0) 0 0/26px 26px, transparent;
    scrollbar-width: none; -ms-overflow-style: none; }
.stkb_cfg::-webkit-scrollbar { display: none; }
.stkb_conn { position: absolute; left: 0; top: 0; pointer-events: none; z-index: 0; }
.stkb_band { position: relative; z-index: 1; display: flex; align-items: stretch; min-width: max-content; }

/* CS CONTAINER — full-width headline on top (hub name + stake short-key, def/tier line), then two GRID-
   aligned meter columns below: [ power/crew/cargo ] | [ ghost/slots/fuel ] — same item count each side,
   bottom-aligned so they line up row for row regardless of either column's natural height. */
.stkb_core { flex: 0 0 auto; margin: 0 26px; display: flex; flex-direction: column; gap: 8px;
    border-radius: 9px; padding: 12px; border: 1px solid color-mix(in srgb, var(--ops-type-cs) 55%, var(--ops-line));
    background: radial-gradient(circle at 50% 24%, color-mix(in srgb, var(--ops-type-cs) 18%, transparent), transparent 62%), color-mix(in srgb, var(--ops-type-cs) 8%, var(--ops-card)); }
.stkb_core_hd { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
    text-align: left; border-bottom: 1px solid color-mix(in srgb, var(--ops-type-cs) 22%, transparent); padding-bottom: 8px; }
/* status (top-right of the headline) — color is set INLINE per-instance (real state → green/warn/red), not a
   fixed class, since it's data-driven now. "Constructing" is a THIRD state in this SAME spot (with a live
   countdown prefix), not a separate second line. */
.stkb_status { font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; flex: 0 0 auto; }
.stkb_core_cols { display: flex; align-items: stretch; gap: 12px; }
.stkb_core_caps { display: flex; flex-direction: column; gap: 8px; justify-content: flex-end; width: 140px; }
.stkb_core_main { display: flex; flex-direction: column; gap: 8px; justify-content: flex-end; width: 140px; text-align: center; }
.stkb_core .ops_meter { background: var(--ops-bg-sunken); border: 1px solid var(--ops-line-soft); border-radius: 6px; padding: 7px 9px; }
.stkb_core .ops_meter_val { font-size: 11px; }   /* a bit tighter than the shared 13px default */

/* category rows — wide (right) share this; small-module rows (left) hug the container, don't grow */
.stkb_rows { flex: 1 1 auto; display: flex; flex-direction: column; gap: 16px; min-width: max-content; }
.stkb_srows { flex: 0 0 auto; }
.stkb_row { flex: 1 1 auto; display: flex; align-items: stretch; gap: 20px; }   /* stretch → all nodes equal height */
/* left (side) rows anchor FLUSH-RIGHT (nearest the CS core) so the anchor tile sits in the same column on
   every row regardless of whether that row's + is shown or how many tiles it holds — the grid-alignment
   counterpart to the wide rows' hub-always-first-child. */
.stkb_srow { justify-content: flex-end; }
/* EXPERIMENT (2026-07-25, may revert) — storage MEMBERS (+ the +) extend DOWNWARD below the Storage Hub tile,
   which stays a normal row above (same height as Power/Crew). `.stkb_srow_vert` is a STANDALONE block (not a
   `.stkb_row`), position:absolute and anchored to `top:100%` of `.stkb_srows` (i.e. right where the hub row
   ends), so it can grow freely downward WITHOUT feeding into the .stkb_band stretch that keeps srows/core/rows
   equal height, and isn't part of equalizeRowHeights — these tiles stay their own (shorter) natural size,
   deliberately NOT matching the rest of the grid. */
.stkb_srows { position: relative; }
.stkb_srow_vert { position: absolute; left: 0; right: 0; top: 100%; margin-top: 16px;
    display: flex; flex-direction: column; align-items: center; gap: 16px; }   /* same gap as the rows above */
.stkb_srow_vert > .ops_node { flex: 0 0 auto; width: 122px; display: flex; flex-direction: column; position: relative; }
/* every node in a row: uniform width, flex column, relative for the absolute controls. Head + body split the
   tile EXACTLY in half (flex:1 1 0 both) so the divider between them always sits at the middle — the storage
   EXTENSION tiles are included too (equalizeRowHeights sets their height to match the grid explicitly). */
.stkb_row > .ops_node { flex: 0 0 122px; width: 122px; display: flex; flex-direction: column; position: relative; }
.stkb_srow_vert > .ops_node > .ops_node_head, .stkb_srow_vert > .ops_node > .ops_node_body { flex: 1 1 0; min-height: 0; }
.stkb_row > .ops_node > .ops_node_head, .stkb_row > .ops_node > .ops_node_body { flex: 1 1 0; min-height: 0; }

/* node headline — allowed to word-wrap (the head/body 50/50 split gives it room now); long unbroken words
   still don't overflow the tile. */
.stkb_title { display: block; white-space: normal; overflow-wrap: break-word; max-width: 100%; }

/* tiles — chrome is the NEUTRAL shared .ops_node (background/border same for every category), matching the
   fleet loop editor's .le_stop cards; category is not tinted at all (per user: move away from coloring
   categories differently). Head/body equal-height split lives with the other tile-sizing rules above. */
.stkb_tile > .ops_node_body { display: flex; align-items: center; }
.stkb_tile { cursor: pointer; }                          /* click the tile body → open it in the right pane */
/* draft-change cues (same yellow as the Build-on-chain button): NEW node = outline; existing count change = +N/−N */
.stkb_tile.stkb_new { border-color: var(--ops-warn); }
.stkb_delta { margin-left: 6px; color: var(--ops-warn); font-weight: 700; font-size: 11px; }
.stkb_tile:hover { border-color: var(--ops-line); }
.stkb_hub { border-width: 2px; }
.stkb_hub_missing { border-style: dashed; border-color: var(--ops-muted); opacity: .7; }
.stkb_addhub { cursor: pointer; }
.stkb_addhub:hover { opacity: 1; border-style: solid; border-color: var(--ops-accent); }
.stkb_addhub:hover .ops_node_body { color: var(--ops-accent); }

/* count badge — floats at a fixed spot (right edge, vertically centered on the body), ABOVE the ctrls overlay
   (z-index) so it never moves or disappears when hover reveals the −/+/× strip. */
.stkb_count { position: absolute; top: 75%; right: 6px; transform: translateY(-50%); color: var(--ops-dim); font-size: 11px; z-index: 2; pointer-events: none; }

/* P1 — member tile controls: ABSOLUTE bottom strip, revealed on hover (so hover never changes tile height) */
.stkb_ctrls { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: center; gap: 3px; justify-content: center; padding: 4px 0;
    background: var(--ops-card); border-top: 1px solid var(--ops-line-soft); opacity: 0; transition: opacity .12s; }
.stkb_tile:hover .stkb_ctrls { opacity: 1; }
.stkb_mini { width: 18px; height: 18px; border-radius: 5px; background: var(--ops-panel2); border: 1px solid var(--ops-line); color: var(--ops-txt); font-size: 12px; line-height: 16px; text-align: center; cursor: pointer; }
.stkb_mini:hover { background: var(--ops-panel2-hi); color: var(--ops-txt-hi); }
.stkb_rm:hover { color: var(--ops-red); border-color: var(--ops-red); }
.stkb_mini.stkb_dis { opacity: .3; pointer-events: none; }                    /* no sibling tier / exceeds stake tier */
/* orphaned member — its cat-hub was removed / never placed (design invalid until the hub is back) */
.stkb_orphan { border-color: var(--ops-red) !important; background: color-mix(in srgb, var(--ops-red) 12%, var(--ops-node-bg)); }
.stkb_orphan .ops_node_head { background: color-mix(in srgb, var(--ops-red) 18%, var(--ops-node-head)); }
/* STARVED building — consumes an input the stake can't self-supply → all-or-nothing halts the WHOLE stake (red) */
.stkb_starved { border-color: var(--ops-red) !important; background: color-mix(in srgb, var(--ops-red) 10%, var(--ops-node-bg)); }
.stkb_thr { color: var(--ops-red); font-weight: 700; }
/* STOPPED banner + "Missing inputs" label in the info band */
.stkb_stopped { color: var(--ops-red); font-weight: 700; }
.stkb_stopped_l { color: var(--ops-red); }
/* placed but research-locked — a SIMULATION-only node (dashed amber; real build gated on research) */
.stkb_needsresearch { border-style: dashed; border-color: color-mix(in srgb, var(--ops-warn) 55%, var(--ops-node-line)); }
.stkb_addbtn { width: 26px; height: 26px; font-size: 16px; align-self: center; }   /* row-end + circle */

/* member/hub PANE — docked to the right of the config area (replaces the old centered-modal picker so it
   never covers the layout being edited). Empty state ("pick a node") before any + has been clicked. */
.stkb_pane { flex: 0 0 320px; width: 320px; display: flex; flex-direction: column; min-height: 0;
    background: var(--ops-panel); border: 1px solid var(--ops-line); border-top: 0; border-radius: 0 0 10px 10px; color: var(--ops-txt); }
.stkb_pane_empty { margin: auto; font-size: 12px; }                    /* centered placeholder, no family focused yet */
/* clicked-node DETAIL view — replaces the family-add picker in the right pane with just that node's info */
.stkb_detail { display: flex; flex-direction: column; min-height: 0; padding: 10px 12px 12px; overflow-y: auto; }
.stkb_detail_hd { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.stkb_detail_nm { color: var(--ops-txt-hi); font-size: 14px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stkb_detail_close { cursor: pointer; color: var(--ops-dim); font-size: 18px; line-height: 1; padding: 0 4px; }
.stkb_detail_close:hover { color: var(--ops-txt-hi); }
.stkb_detail_meta { margin-bottom: 8px; }
.stkb_detail_row { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; color: var(--ops-txt); margin-bottom: 2px; }
.stkb_detail_ctrls { position: static; opacity: 1; background: transparent; border: 0; padding: 0; width: auto; justify-content: flex-end; }
.stkb_detail_sect { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--ops-line-soft); }
.stkb_detail_sect .ops_label { display: block; margin-bottom: 4px; }
.stkb_detail_flag { color: var(--ops-red); font-size: 11px; font-weight: 600; padding: 2px 0; }
/* top section — Reset + build-cost delta (shown only when modified); also where future commit/action
   buttons land once P2 (on-chain commit) is wired. */
.stkb_pane_top { flex: 0 0 auto; padding: 10px 10px 8px; border-bottom: 1px solid var(--ops-line-soft); }
.stkb_pane_top_row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.stkb_pane_top .stkb_buildcost { padding: 0; }
.stkb_pane_hd { padding: 10px 10px 4px; }
/* extractor-picker "extractable here only" toggle (default on) */
.stkb_pane_toggle { display: flex; align-items: center; gap: 6px; margin: 0 10px 4px; font-size: 11px; color: var(--ops-txt); cursor: pointer; user-select: none; }
.stkb_pane_toggle input { cursor: pointer; margin: 0; }
.stkb_pane_search { margin: 4px 10px 6px; background: var(--ops-bg-sunken); border: 1px solid var(--ops-line); border-radius: 6px; color: var(--ops-txt); font-size: 12px; padding: 6px 9px; outline: none; }
.stkb_pane_search:focus { border-color: var(--ops-accent); }
.stkb_pane_list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 0 6px 6px; scrollbar-width: none; -ms-overflow-style: none; }
.stkb_pane_list::-webkit-scrollbar { display: none; }
/* candidate CARD — name / meta / does-block, stacked at the fixed pane width. Brighter than the pane
   background BY DEFAULT (so cards read as distinct tiles at rest); hover only nudges the tint a little
   further + brightens the border, rather than a big jump from a flat/dim resting state. */
.stkb_pane_it { display: flex; flex-direction: column; gap: 2px; padding: 7px 9px; margin-bottom: 5px; border-radius: 6px; cursor: pointer; font-size: 12px;
    border: 1px solid var(--ops-line-soft); background: var(--ops-overlay-06); }
.stkb_pane_it[data-id]:hover { background: var(--ops-overlay-10); border-color: var(--ops-line); }
.stkb_pane_it .nm { color: var(--ops-txt-hi); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* per-building behavior block — one fixed-width flow row per resource: [sign · name(ellipsis) · rate-right] */
.stkb_pane_does { display: flex; flex-direction: column; gap: 2px; margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--ops-line-soft); }
.stkb_flow { display: flex; align-items: baseline; gap: 5px; font-size: 11px; line-height: 1.35; color: var(--ops-txt); }
.stkb_flow_s { flex: 0 0 auto; width: 8px; font-weight: 700; }
.stkb_flow_s.pos { color: var(--ops-green); }
.stkb_flow_s.neg { color: var(--ops-warn); }
.stkb_flow_n { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stkb_flow_r { flex: 0 0 auto; color: var(--ops-dim); font-variant-numeric: tabular-nums; }
.stkb_pane_it.stkb_locked { cursor: not-allowed; opacity: .5; }         /* member gated on a missing hub */
.stkb_pane_it.stkb_locked:hover { background: transparent; }
/* last-picked — accent border so it's visually clear which node just got placed */
.stkb_pane_it.stkb_pane_last { border-color: var(--ops-accent); background: color-mix(in srgb, var(--ops-accent) 10%, transparent); }
.stkb_lockmsg { color: var(--ops-red); font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .4px; }
/* research-gated candidate — its own row (before the material rows) with the Unlock/Tree buttons pinned right.
   NOT dimmed (unlike a structural no-hub lock) so the unlock buttons stay live. */
.stkb_pane_it.stkb_research_gated { border-color: color-mix(in srgb, var(--col-stopped) 30%, var(--ops-line-soft)); }
.stkb_pane_research { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--ops-line-soft); }
.stkb_research_btns { display: flex; gap: 4px; flex: 0 0 auto; }
.stkb_research_btns .btn_cmd_simple { font-size: 10px; }
/* PLANET-RESOURCE gate — shown but DISABLED (the planet can't supply the extracted resource, no user remedy),
   with a reason row and production details omitted. Reuses the locked dim; red-tinted border to read as blocked. */
.stkb_pane_it.stkb_planet_gated { cursor: not-allowed; opacity: .5; border-color: color-mix(in srgb, var(--ops-red) 40%, var(--ops-line-soft)); }
.stkb_pane_it.stkb_planet_gated:hover { background: transparent; }
.stkb_pane_planet { margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--ops-line-soft); }
.stkb_reset { padding: 1px 8px; font-size: 11px; }   /* pane top row */
/* on-chain COMMIT (P2) — prominent yellow "Build on chain" occupies the pane-top row (in place of the old
   "unsaved changes" text); Reset sits to its right. One click sims then, on pass, does the real place+finalize. */
.stkb_build_btn { flex: 1 1 auto; text-align: center; padding: 4px 10px; font-size: 12px; font-weight: 700;
    letter-spacing: .3px; background: var(--ops-warn); border-color: var(--ops-warn); color: var(--ops-bg-sunken); }
.stkb_build_btn:hover { background: var(--ops-warn); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ops-warn) 40%, transparent); }
.stkb_build_btn.dim { opacity: .7; cursor: default; pointer-events: none; }
.stkb_commit_err { color: var(--ops-red); margin-top: 6px; }

/* Drain authoring: "leave N crew behind here" on an embark. The sink toggle on a disembark reuses the
   existing `.le_cond` checkbox (claim-stake Conditional) rather than adding a second one.
   ⚠ Needs the #ops_container prefix: `#ops_container .le_cfg input { width: 100% }` (id specificity)
   otherwise wins and the field stretches across the pane. The pax ROW inputs get away with a plain
   class only because their grid track pins the width. */
#ops_container .le_cfg .le_paxdrain,
#ops_container .le_cfg .le_paxtotal { width: 44px; min-width: 0; }
