:root {
  --font-sans: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: "IBM Plex Mono", "SF Mono", "Consolas", ui-monospace, monospace;
  --r-sm: 4px; --r-md: 7px; --r-lg: 11px;
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px; --sp-6: 32px;
  --grid: 12px;
  
  /* Aliases for existing SVG and UI logic */
  --line: var(--border);
  --ink: var(--text);
  --panel: var(--surface-2);
  --card: var(--surface);
  --muted: var(--text-dim);
  --danger: #e2614b;
  --warn: #d2a433;
  --good: #3ea97e;
  --bg2: var(--surface-3);
  --mono: var(--font-mono);
}

[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0e1116; --bg-grid: #171b22; --surface: #161b22; --surface-2: #1c222b; --surface-3: #232a34;
  --border: #2a323d; --border-soft: #222831; --text: #dbe3ec; --text-dim: #8b97a6; --text-faint: #5a6472; --num: #e8eef5;
  --note-bg: #746c24; --note-text: #f0f0f0;
  --accent: #4fb8c9; --accent-dim: #2d6c77; --accent-bg: #142227;
  --buck: #6f9fd8; --ldo: #c9a86a; --switch: #8a94c2; --icr: #b98a8a;
  --loss: #c98a6a; --load: #6fb08a; --ddr: #b0a06a;
  /* what a node IS - see the block at ".node kinds" below for the reasoning */
  --k-source: #9871d5; --k-conv: #0086af; --k-lin: #987000;
  --k-load: #008e53; --k-emit: #d05a6e;
  /* what a wire CARRIES - one hue, five steps, see ".wire bands" below */
  --w-1: #0070a6; --w-2: #00a38f; --w-3: #607200;
  --w-4: #cc7200; --w-5: #ad3665; --w-6: #9065d0;
  /* bracket depth: purple / lime / blue - hues the six reference colours above do not use, so a bracket never reads as a range */
  --cs-par-0: #b98ce0; --cs-par-1: #9fce55; --cs-par-2: #6f9fd8;
  /* Figure series, in fixed order. Not --accent/--loss: those sit above the
     dark lightness band and below the chroma floor, so as adjacent fills they
     read washed out. These are the same two hue families stepped down until the
     validator passes every check against the dark surface. */
  --dv-1: #00a5c0; --dv-2: #c96a2e; --dv-3: #8f74d8;
  --dv-4: #5f9e3f; --dv-5: #cf5f8f; --dv-6: #4a7fd0;
  --dv-7: #b08f2b; --dv-8: #b4519d; --dv-9: #2f9f78;
  --bar-track: #232a34;
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 6px 20px rgba(0,0,0,.28);
  --node-shadow: 0 1px 0 rgba(255,255,255,.02), 0 2px 8px rgba(0,0,0,.35);
}
[data-theme="light"] {
  color-scheme: light;
  --bg: #f4f6f8; --bg-grid: #eaeef2; --surface: #ffffff; --surface-2: #f7f9fb; --surface-3: #eef2f6;
  --border: #d6dde4; --border-soft: #e4e9ee; --text: #1f2933; --text-dim: #5a6774; --text-faint: #93a1af; --num: #14202b;
  --note-bg: #fdfd96; --note-text: #222222;
  --accent: #2f8d9e; --accent-dim: #8ecad4; --accent-bg: #e7f2f4;
  --buck: #3f6fae; --ldo: #a9843b; --switch: #5b64a0; --icr: #a5605f;
  --loss: #b86b45; --load: #40875f; --ddr: #8f7f3e;
  --k-source: #875cc6; --k-conv: #0090bb; --k-lin: #987000;
  --k-load: #008e53; --k-emit: #d4556c;
  --w-1: #006191; --w-2: #009280; --w-3: #536300;
  --w-4: #b76600; --w-5: #9f1d57; --w-6: #7747b7;
  --cs-par-0: #7d45a8; --cs-par-1: #5c8f21; --cs-par-2: #3f6fae;
  --dv-1: #008b9f; --dv-2: #b85f1e; --dv-3: #6b4fbf;
  --dv-4: #3d7f2c; --dv-5: #b03a6b; --dv-6: #2f62b0;
  --dv-7: #96781f; --dv-8: #9a3f85; --dv-9: #1f8763;
  --bar-track: #e4e9ee;
  --shadow: 0 1px 2px rgba(16,32,48,.06), 0 6px 18px rgba(16,32,48,.06);
  --node-shadow: 0 1px 3px rgba(16,32,48,.08);
}

* { box-sizing: border-box; }
html { -webkit-font-smoothing: antialiased; font-family: var(--font-sans); }

/* Form controls never inherit the page typeface on their own. Without this every
   button / select / input in the topbar, builder, calc sheet and dialogs falls back
   to the browser's UI font instead of the design typeface. */
button, input, select, textarea, optgroup, option { font-family: inherit; }
::file-selector-button { font-family: inherit; }
body { 
  margin: 0; display: flex; flex-direction: column; height: 100vh; overflow: hidden;
  font-family: var(--font-sans); 
  background-color: var(--bg);
  background-image: linear-gradient(var(--bg-grid) 1px, transparent 1px), linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
  background-size: var(--grid) var(--grid);
  color: var(--text); line-height: 1.45; font-size: 13px; letter-spacing: .01em;
  transition: background-color .35s ease, color .35s ease;
}

/* Custom Checkboxes */
input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 15px; height: 15px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background-color: var(--surface-2);
  cursor: pointer;
  vertical-align: middle;
  transition: all 0.2s ease;
  margin: 0;
}
input[type="checkbox"]:hover { border-color: var(--accent-dim); }
input[type="checkbox"]:checked {
  background-color: var(--accent);
  border-color: var(--accent);
  background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M4 7.5L6 9.5L10.5 4" stroke="%23ffffff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>');
  background-repeat: no-repeat;
  background-position: center;
}

/* Custom Dropdowns */
select {
  appearance: none; -webkit-appearance: none;
  background-image: url('data:image/svg+xml;utf8,<svg fill="%238b97a6" height="20" viewBox="0 0 24 24" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/></svg>');
  background-repeat: no-repeat;
  background-position: right 4px center;
  padding: 4px 22px 4px 8px;
  background-color: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-family: var(--font-sans);
  font-size: 13px;
  cursor: pointer;
}
select:hover { border-color: var(--accent-dim); }
select:focus { outline: 1.5px solid var(--accent); border-color: transparent; }


#topbar {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface); border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow); position: relative; z-index: 20; backdrop-filter: blur(6px);
  font-size: 13px;
  flex-wrap: wrap;
}
.tb-group { display: flex; align-items: center; gap: 8px; }
.tb-divider { width: 1px; height: 24px; background: var(--border-soft); margin: 0 4px; }
.mode-box { background: var(--surface-2); padding: 4px 6px; border-radius: var(--r-md); border: 1px solid var(--border-soft); gap: 4px; }
.mode-box label { display: flex; align-items: center; gap: 6px; font-weight: 600; color: var(--text-dim); }
.mode-box select { padding: 4px 24px 4px 8px; font-size: 13px; font-weight: 600; background: var(--surface-3); border: none; border-radius: var(--r-sm); color: var(--text); }
.tb-btn { font-size: 13px; font-weight: 600; padding: 6px 12px; cursor: pointer; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-2); color: var(--text); transition: all .2s ease; display: inline-flex; align-items: center; justify-content: center; }
.tb-btn:hover { background: var(--surface-3); border-color: var(--border-hover); }
/* The manual is a page, not a dialog, so it is a link - and a link wearing the
   button shape must not also wear an underline. */
a.tb-btn { text-decoration: none; }
.tb-btn.primary, .tb-btn.active { background: var(--accent); color: white; border-color: transparent; }
.tb-btn.primary:hover, .tb-btn.active:hover { filter: brightness(1.1); box-shadow: 0 2px 8px rgba(47,141,158,0.3); }
.tb-icon-btn { display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; font-size: 14px; cursor: pointer; border: none; border-radius: var(--r-sm); background: transparent; color: var(--text-dim); transition: all .2s ease; }
.tb-icon-btn:hover { background: var(--surface-3); color: var(--text); }

#topbar select { font-size: 13px; padding: 2px 6px; background: var(--surface-2); color: var(--text); border: 1px solid var(--border); border-radius: var(--r-sm); }
.battery { font-family: var(--mono); font-weight: 700; color: var(--accent); background: var(--accent-bg); padding: 4px 10px; border-radius: var(--r-md); border: 1px solid var(--border-soft); font-size: 12.5px; white-space: nowrap; }
.spacer { flex: 1; }
.hint { color: var(--text-faint); font-size: 11px; }
.muted { color: var(--text-dim); }

/* ---- board tabs ---- */
#boardtabs { display: flex; align-items: center; gap: 4px; padding: 4px 10px; background: var(--surface); border-bottom: 1px solid var(--border); }
#boardtabs .tab { font-size: 12px; padding: 4px 12px; border: 1px solid var(--border); border-bottom: none; border-radius: var(--r-md) var(--r-md) 0 0; background: var(--surface-2); color: var(--text-dim); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
#boardtabs .tab.active { background: transparent; color: var(--text); font-weight: 600; border-color: var(--accent); }
#boardtabs .tab.addtab { color: var(--accent); font-weight: 700; padding: 4px 10px; }
#boardtabs .tabx { border: none; background: none; color: var(--text-dim); cursor: pointer; font-size: 13px; padding: 0 2px; line-height: 1; }
#boardtabs .tabx:hover { color: var(--danger); }

/* ---- cross-board off-page connectors ---- */
.stub { cursor: pointer; }
.stub .sbox { fill: var(--bg2, #eef); stroke: var(--accent); stroke-width: 1.3; stroke-dasharray: 3 2; }
.stub:hover .sbox { fill: var(--accent-bg, #eeedfe); }
.stub .sdir { font: 700 9px var(--mono); fill: var(--accent); }
.stub .slink { cursor: pointer; }
.stub .slink:hover { text-decoration: underline; fill: var(--accent-hover, #4a47d2); }
.stub .snet { font: 9px var(--mono); fill: var(--ink); }
.stub .snet-rail { fill: var(--accent); font-weight: 700; }

main { flex: 1; display: flex; min-height: 0; }

.theme-toggle { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border); border-radius: 100px; background: var(--surface-2); padding: 3px; cursor: pointer; user-select: none; }
.theme-toggle button { border: 0; background: transparent; color: var(--text-faint); font: inherit; font-size: 11px; padding: 5px 11px; border-radius: 100px; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; transition: all .2s ease; }
.theme-toggle button.active { background: var(--surface-3); color: var(--text); box-shadow: var(--node-shadow); }
[data-theme="dark"] .theme-toggle button.active { color: var(--accent); }

/* ---- results dashboard ---- */
#results, #batteryPage { flex: 1; overflow: auto; padding: 14px 18px; background: transparent; }
#results h2, #batteryPage h2 { font-size: 15px; margin: 0 0 10px; }
.kpi { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-4) var(--sp-4) var(--sp-3); box-shadow: var(--shadow); position: relative; overflow: hidden; }
.kpi::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--accent); opacity: .6; }
.kpi.loss::before { background: var(--loss); }
.kpi.load::before { background: var(--load); }
.kpi.eff::before { background: var(--buck); }
.kpi .label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .09em; color: var(--text-dim); margin-bottom: var(--sp-2); }
.kpi .value { font-family: var(--font-mono); font-size: 27px; font-weight: 500; color: var(--num); line-height: 1; }
.kpi .value .unit { font-size: 13px; font-weight: 400; }
.kpi .meta { font-size: 11px; color: var(--text-faint); margin-top: var(--sp-2); font-family: var(--font-mono); }
.kpi .spark { margin-top: var(--sp-3); height: 4px; border-radius: 4px; background: var(--bar-track); overflow: hidden; }
.kpi .spark > i { display: block; height: 100%; border-radius: 4px; }
.res-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
.res-col h3 { font-size: 12px; color: var(--muted); letter-spacing: .05em; margin: 0 0 8px; border-bottom: 1px solid var(--border); padding-bottom: 4px; }
.load-item { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px dashed var(--border-soft); }
.load-item .ln { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 0 150px; }
.load-item .lref { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); }
.load-item .lbar { flex: 1; height: 5px; border-radius: 3px; background: var(--bar-track); overflow: hidden; }
.load-item .lbar > i { display: block; height: 100%; border-radius: 3px; }
.load-item .lp { font-family: var(--font-mono); font-size: 11.5px; color: var(--num); min-width: 64px; text-align: right; }
/* Which slice of the mode this page is about - a note on the heading, not a
   second heading: the mode is still what is being looked at. */
.res-slice { color: var(--text-dim); font-weight: 500; }
.res-modes { width: 100%; border-collapse: collapse; margin-bottom: 14px; font-size: 11px; }
.res-modes th, .res-modes td { border: 1px solid var(--border); padding: 4px 8px; text-align: right; }
.res-modes th:first-child, .res-modes td:first-child { text-align: left; }
.res-modes tr.cur { background: var(--accent-bg); font-weight: 600; }
.res-modes th { background: var(--surface-2); color: var(--text-dim); font-weight: 700; }

/* ---- battery page: the mission profile is typed, not just read ---- */
/* The house input look lives under #builder, so a number box anywhere else fell
   back to the browser's own - which is why one sat next to a styled dropdown
   looking like it came from a different program. */
.bat-field { display: inline-flex; align-items: center; gap: 6px; }
.bat-num {
  width: 76px; text-align: right;
  font-family: var(--font-mono); font-size: 12px; padding: 5px 7px;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface-2); color: var(--text);
  outline: none; transition: border-color .2s, background .2s;
}
.bat-num:hover { border-color: var(--accent-dim); }
/* A duration taken from a component's sheet is shown, not typed: it reads as a
   value the page computed, and the picker beside it says where it came from. */
.bat-num[readonly] {
  background: transparent; color: var(--text-dim); border-style: dashed; cursor: default;
}
.bat-num[readonly]:hover { border-color: var(--border); }
.bat-num:focus { border-color: transparent; outline: 1.5px solid var(--accent); background: var(--surface-3); }
/* Wide enough that "min" does not crowd the arrow, narrow enough to read as a
   unit rather than a second field. */
.bat-unit { min-width: 64px; }

/* ---- a mode cut into slices ----
   The slices belong to the row above them and have to read that way: indented,
   on a quieter ground, and with a rule down the left that runs from the mode to
   the last of them. Without it a table of nine columns reads as nine more modes
   whose currents happen not to add up. */
.res-modes tr.has-seg td { border-bottom-color: transparent; }
.res-modes tr.seg-row td { background: var(--surface-2); border-top-color: transparent; }
.res-modes tr.seg-row td:first-child { padding-left: 22px; position: relative; }
.res-modes tr.seg-row td:first-child::before {
  content: ""; position: absolute; left: 10px; top: 0; bottom: 0;
  border-left: 2px solid var(--border);
}
.res-modes tr.seg-row:last-child td:first-child::before { bottom: 50%; }
.res-modes tr.seg-row td:first-child::after {
  content: ""; position: absolute; left: 10px; top: 50%; width: 6px;
  border-top: 2px solid var(--border);
}
.seg-n {
  width: 100%; min-width: 90px; font: inherit; color: var(--text);
  background: transparent; border: 1px solid transparent; border-radius: 5px;
  padding: 3px 5px; outline: none;
}
.seg-n:hover { border-color: var(--border); }
.seg-n:focus { border-color: transparent; outline: 1.5px solid var(--accent); background: var(--surface-3); }
.seg-n::placeholder { color: var(--text-dim); font-style: italic; }
/* Disabled rather than hidden when the draw is left to the tree: the box is
   where the number goes, and taking it away on "as solved" makes the row jump. */
.seg-v:disabled { background: transparent; border-style: dashed; color: var(--text-dim); }
/* Both live in cells the table right-aligns; these two are controls, not
   figures, and read better against the column they belong to. */
.res-modes tr.seg-row td.seg-draw { text-align: left; }
/* Three controls in one cell - the number, what it is, and whose it is - which
   is one thought and belongs together. On a narrow window they wrap rather than
   pushing the columns of figures off the table. */
.res-modes tr.seg-row td.seg-draw .bat-field { flex-wrap: wrap; }
.seg-row .seg-q, .seg-row .seg-at, .seg-row .seg-src { max-width: 136px; }
.res-modes tr.seg-row td.seg-x { padding: 0 4px; }
.seg-plus, .seg-del {
  font: inherit; color: var(--text-dim); background: transparent;
  border: 1px solid var(--border); border-radius: 5px; cursor: pointer;
  padding: 1px 6px; line-height: 1.5; transition: color .15s, border-color .15s;
}
.seg-plus { margin-left: 8px; opacity: 0; }
/* Out of the way until the row is under the pointer - one per mode is a column
   of buttons down a table that is meant to be read. Focus brings it back for
   anyone arriving by keyboard. */
.res-modes tr:hover .seg-plus, .seg-plus:focus { opacity: 1; }
.seg-plus:hover, .seg-del:hover { color: var(--accent); border-color: var(--accent); }
.seg-del:hover { color: var(--danger); border-color: var(--danger); }

/* ---- treemaps ---- */

/* ---- shunts page ---- */
#shunts { flex: 1; overflow: auto; padding: 14px 18px; background: transparent; }
#shunts h2 { font-size: 15px; margin: 0 0 10px; }
.sh-tbl { width: 100%; border-collapse: collapse; font-size: 11px; }
.sh-tbl th, .sh-tbl td { border: 1px solid var(--border); padding: 3px 6px; text-align: right; font-family: var(--font-mono); }
.sh-tbl th { background: var(--surface-2); color: var(--text-dim); font-weight: 700; text-align: center; }
.sh-tbl td:first-child, .sh-tbl td:nth-child(2) { text-align: left; }
.sh-tbl input { width: 100%; border: none; background: transparent; color: var(--text); font: inherit; text-align: inherit; padding: 1px 2px; }
.sh-tbl input:focus { outline: 1.5px solid var(--accent); border-radius: 3px; background: var(--surface-3); }
.sh-tbl select { width: 100%; border: none; background: transparent; color: var(--text); font: inherit; padding: 1px 2px; }
.sh-tbl select:focus { outline: 1.5px solid var(--accent); border-radius: 3px; background: var(--surface-3); }
.sh-tbl .comp { color: var(--text-dim); background: var(--surface-2); }
.sh-tbl .mdel { border: none; background: none; color: var(--text-dim); cursor: pointer; }
.sh-tbl .mdel:hover { color: var(--danger); }
#shunts .addbtn { margin-top: 10px; }

/* ---- component calc sheet (jspreadsheet overrides) ---- */
/* jspreadsheet/jsuites ship their own font stack in the vendor CSS, so the grid
   and every popup it spawns have to be pulled back onto the app typeface.
   Grid body: sans like a spreadsheet, with tabular figures so digits still line
   up column-wise. Row/column headers and all popup chrome: plain sans. */
.jexcel_container, .jexcel_content, table.jexcel,
table.jexcel > tbody > tr > td,
.jexcel_container input, .jexcel_container textarea, .jexcel_container select {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
}
table.jexcel > thead > tr > td,
table.jexcel > tbody > tr > td.jexcel_row,
.jexcel_corner, .jexcel_contextmenu, .jexcel_filter,
.jdropdown, .jdropdown-container, .jcontextmenu, .jtabs, .jtoolbar,
.jcalendar, .jsuites_dropdown, .jsuites_calendar, .jsuites_tooltip {
  font-family: var(--font-sans);
  font-variant-numeric: normal;
}
.jexcel-wrapper { overflow-x: auto; border-radius: 6px; border: 1px solid var(--border); margin-bottom: 10px; }
html[data-theme="dark"] .jexcel > thead > tr > td,
html[data-theme="dark"] .jexcel > tbody > tr > td.jexcel_row { background-color: var(--surface-2) !important; color: var(--text-dim) !important; border-color: var(--border) !important; }
html[data-theme="dark"] .jexcel > tbody > tr > td { background-color: var(--surface) !important; color: var(--text) !important; border-color: var(--border) !important; }
html[data-theme="dark"] .jexcel > tbody > tr > td.highlight { background-color: var(--surface-3) !important; }
html[data-theme="dark"] .jexcel > tbody > tr > td.highlight-left,
html[data-theme="dark"] .jexcel > tbody > tr > td.highlight-right,
html[data-theme="dark"] .jexcel > tbody > tr > td.highlight-top,
html[data-theme="dark"] .jexcel > tbody > tr > td.highlight-bottom { border-color: var(--accent) !important; }

html[data-theme="dark"] .jexcel .selection { background-color: rgba(255, 255, 255, 0.1) !important; }
html[data-theme="dark"] .jexcel .selection-left,
html[data-theme="dark"] .jexcel .selection-right,
html[data-theme="dark"] .jexcel .selection-top,
html[data-theme="dark"] .jexcel .selection-bottom { border-color: var(--accent) !important; }

/* calc-sheet inputs in the node panel: a field that overrides the shared value
   for the selected mode is marked, so "inherited" and "set here" never blur */
.sheet-in.ovr { border-color: var(--accent) !important; color: var(--accent); }

/* The way into the calc sheet - one of the tool's main features, so it gets a
   card of its own in the builder instead of a small link-sized button. */
.cs-entry { display: flex; align-items: center; gap: 16px; padding: 13px 16px;
  border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface-2);
  position: relative; overflow: hidden; }
.cs-entry::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent); opacity: .45; }
.cs-entry.on::before { opacity: 1; }
.cs-entry-txt { flex: 1; min-width: 0; }
.cs-entry-hd { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.cs-entry-hd b { font-size: 13px; color: var(--text); }
.cs-entry-sum { font-size: 10.5px; color: var(--text-dim); }
.cs-entry-txt p { margin: 4px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--text-dim); }
.cs-entry-btn { flex: none; font: inherit; font-size: 13px; font-weight: 600;
  height: 34px; padding: 0 16px; border: 0; border-radius: var(--r-md); cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--accent); color: #fff;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 32%, transparent);
  transition: filter .15s ease, box-shadow .15s ease; }
.cs-entry-btn:hover { filter: brightness(1.08);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 40%, transparent); }
.cs-entry-btn:active { filter: brightness(.97); }
.cs-entry-btn svg { flex: none; opacity: .9; }

/* ---- mission timeline (js/battery.js) ----
   A picture of the profile, not an input to it: the solve never reads the order
   of the modes, so dragging these blocks rearranges the page and nothing else. */
.tl { margin-top: 16px; }
.tl-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
.tl-title { font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-dim); }
.tl-head .muted { font-size: 11.5px; }
.tl-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.tl-lane { flex: none; width: 92px; text-align: right; font-size: 11.5px;
  color: var(--text-dim); white-space: nowrap; }
.tl-bar {
  flex: 1; min-width: 0; display: flex; gap: 2px; height: 30px;
  padding: 2px; border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface-2); overflow: hidden;
}
/* The one-off lane is off the clock, so it is drawn as a note rather than as
   part of the period: no fill of its own, and it never fills the width. */
.tl-once { background: none; border-style: dashed; }
.tl-once .tl-seg { background: none; border: 1px dashed var(--border);
  color: var(--text-dim); }
.tl-once { max-width: 320px; }
.tl-seg {
  min-width: 0; display: flex; align-items: center; overflow: hidden;
  padding: 0 6px; border-radius: 3px; cursor: grab;
  background: var(--tl-c); color: #fff; font-size: 11.5px; user-select: none;
}
.tl-seg > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tl-seg:hover { filter: brightness(1.12); }
/* A block too narrow for its name carries none: half a word inside a sliver is
   noise shaped like a label. The key below names it instead. */
.tl-seg.tl-narrow { padding: 0; }
.tl-seg.tl-narrow > span { display: none; }

/* The key: the strip's own order and colours, with the real time and share. */
.tl-key-row { margin-top: -3px; }
.tl-key { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: 3px 15px;
  font-size: 11.5px; color: var(--text-dim); }
.tl-key-i { display: inline-flex; align-items: baseline; gap: 5px; white-space: nowrap; }
.tl-key-i i { width: 8px; height: 8px; border-radius: 2px; flex: none; align-self: center; }
.tl-key-i b { font-weight: 400; color: var(--text); font-variant-numeric: tabular-nums; }
.tl-key-i em { font-style: normal; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.tl-key-i u { text-decoration: none; color: var(--text-faint); font-size: 10px;
  border: 1px solid var(--border); border-radius: 3px; padding: 0 3px; }

/* Hovering either half lights the other, so a two-pixel block can be found. */
.tl-seg.tl-lit { filter: brightness(1.2); outline: 1.5px solid var(--text);
  outline-offset: -1px; }
.tl-key-i.tl-lit { color: var(--text); }
.tl-key-i.tl-lit i { box-shadow: 0 0 0 2px var(--surface), 0 0 0 3px var(--text); }
.tl-dragging { cursor: grabbing; outline: 2px solid var(--text); outline-offset: -2px; }
.tl-note { margin-top: 2px; font-size: 11px; }

/* ---- floating datasheet viewer (js/dsviewer.js) ----
   Above the calculation sheet on purpose: it is opened from inside that window
   and its whole job is to stay readable while the grid behind it is typed in. */
.dsv {
  display: none; position: fixed; top: 60px; right: 40px; z-index: 9999;
  width: 460px; height: 360px; min-width: 260px; min-height: 200px;
  overflow: hidden; flex-direction: column;
  /* The header measures itself against THIS, not the browser window: the whole
     point of the thing is that it is dragged to whatever width fits beside the
     grid, and at 300 px it has to look considered rather than broken. */
  container-type: inline-size;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--shadow-lg);
}
.dsv:focus { outline: none; }
/* Grips, not a corner: they sit just inside the border so the window is clipped
   the way it always was, and they are the width of a border plus a few pixels
   of forgiveness - the same target Windows gives an edge. The corners come
   after the edges in the source, so they win where they overlap. */
.dsv-grip { position: absolute; z-index: 3; }
.dsv-grip.n { top: 0; left: 0; right: 0; height: 5px; cursor: ns-resize; }
.dsv-grip.s { bottom: 0; left: 0; right: 0; height: 5px; cursor: ns-resize; }
.dsv-grip.w { left: 0; top: 0; bottom: 0; width: 5px; cursor: ew-resize; }
.dsv-grip.e { right: 0; top: 0; bottom: 0; width: 5px; cursor: ew-resize; }
.dsv-grip.nw, .dsv-grip.ne, .dsv-grip.sw, .dsv-grip.se { width: 12px; height: 12px; }
.dsv-grip.nw { top: 0; left: 0; cursor: nwse-resize; }
.dsv-grip.ne { top: 0; right: 0; cursor: nesw-resize; }
.dsv-grip.sw { bottom: 0; left: 0; cursor: nesw-resize; }
.dsv-grip.se { bottom: 0; right: 0; cursor: nwse-resize; }
.dsv-head {
  display: flex; align-items: center; gap: 4px; padding: 5px 6px 5px 10px;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
  cursor: move; font-size: 12px; user-select: none;
}
/* One line, always. Nothing here wraps and no button squashes; the file's name
   is the one thing that gives up room, by ellipsis. */
.dsv-title, .dsv-count, .dsv-zoom, .dsv-head .dsv-btn { flex: none; white-space: nowrap; }
.dsv-title { color: var(--text-dim); font-size: 11px; letter-spacing: .04em;
  text-transform: uppercase; }
.dsv-name { color: var(--text); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 38%; flex: 0 1 auto; min-width: 0; }
.dsv-count { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.dsv-sp { flex: 1 1 0; }
.dsv-zoom { display: inline-flex; align-items: center; gap: 2px; }
.dsv-zv { min-width: 44px; font-variant-numeric: tabular-nums; }
/* ...and past the width where they all fit, things leave in the order they
   stop earning the room. The word DATASHEET goes first - a window full of
   datasheet pages is not mistakable for anything else - then the word
   "attachments", which the count reads fine without, then the file's name,
   which the page number stands in for, and last the open-in-a-tab arrow.
   The buttons that steer the thing are never among them. */
@container (max-width: 430px) { .dsv-title { display: none; } }
@container (max-width: 370px) { .dsv-word { display: none; } }
@container (max-width: 330px) { .dsv-name { display: none; } }
@container (max-width: 300px) { #dsvOpen { display: none; } }
.dsv-btn {
  background: none; border: 1px solid transparent; border-radius: 4px;
  color: var(--text); cursor: pointer; font-size: 13px; line-height: 1;
  padding: 3px 6px; text-decoration: none;
}
.dsv-btn:hover { background: var(--surface-3); border-color: var(--border); }
.dsv-btn.on { background: var(--accent-bg); border-color: var(--accent-dim); color: var(--accent); }
.dsv-btn:disabled { opacity: .3; cursor: default; background: none; border-color: transparent; }
.dsv-body {
  position: relative;                   /* offsetLeft/Top anchor the wheel zoom */
  flex: 1; min-height: 0; overflow: auto; display: flex;
  /* "safe", or a picture zoomed past the window is centred into its own
     overflow: half of it hangs off the left, where scrollLeft cannot go and
     the scroll width does not count it, so dragging reaches the middle and the
     right of the image and never its left. Safe centring falls back to the
     start the moment it does not fit, which is the only edge a scroll
     container has. */
  align-items: flex-start; justify-content: safe center;
  padding: 4px; background: var(--surface-3);
  overscroll-behavior: contain;         /* the page behind must not scroll too */
}
.dsv-body.pannable { cursor: grab; }
.dsv-body.panning { cursor: grabbing; }
/* An explicit display beats the hidden attribute, so both of these have to
   say so themselves - or the empty <img> keeps its box behind a PDF and under
   the pool. */
.dsv-body img[hidden], .dsv-body iframe[hidden] { display: none; }
/* flex: none, or the zoomed image is squeezed back to the window by the flex
   container instead of overflowing it and letting the body scroll. */
.dsv-body img {
  display: block; flex: none; max-width: 100%; max-height: 100%;
  user-select: none; -webkit-user-drag: none;
}
.dsv-body iframe { flex: none; width: 100%; height: 100%; border: 0; background: #fff; }
.dsv-cap { padding: 5px 10px; border-top: 1px solid var(--border);
  color: var(--text-dim); font-size: 11.5px; }

/* ---- the pool: every attachment at once ----
   A block, not the centring flex the single picture wants: a contact sheet
   starts at the top-left corner and scrolls, it is not hung in the middle. */
.dsv-body.pool { display: block; padding: 8px; }
.dsv-grid {
  display: grid; gap: 8px; align-items: start;
  grid-template-columns: repeat(var(--dsv-cols, 2), minmax(0, 1fr));
}
.dsv-grid[hidden] { display: none; }
.dsv-tile {
  display: flex; flex-direction: column; gap: 4px; padding: 4px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-sm); cursor: zoom-in; text-align: left;
  font: inherit; color: inherit;
}
.dsv-tile:hover { border-color: var(--accent-dim); }
.dsv-tile.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
/* Every tile the same height, whatever shape the page is: a contact sheet
   whose rows line up can be read down a column, and one built from the pages'
   own proportions is a ragged wall with holes in it. The picture is contained
   rather than cropped - a datasheet table cropped to a square is a table with
   its numbers cut off. */
.dsv-tile img {
  width: 100%; height: var(--dsv-th, 190px); max-width: 100%; max-height: none;
  object-fit: contain; border-radius: 2px; background: var(--surface-3);
}
.dsv-file {
  display: flex; align-items: center; justify-content: center;
  height: var(--dsv-th, 190px); font-size: 26px; background: var(--surface-3);
  border-radius: 2px;
}
.dsv-tname {
  font-size: 10.5px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- Excel-like calculation canvas (js/calcsheet.js) ---- */
.cs-modal { position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.45);
  display: flex; align-items: center; justify-content: center; }
.cs-modal[hidden] { display: none; }
.cs-win { width: 96vw; height: 92vh; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow); display: flex; flex-direction: column; overflow: hidden; }
.cs-head { display: flex; align-items: center; gap: 8px; padding: 7px 12px;
  border-bottom: 1px solid var(--border); background: var(--surface-2); font-size: 13px; }
.cs-title { display: flex; align-items: baseline; gap: 7px; font-weight: 600;
  color: var(--text-dim); white-space: nowrap; margin-right: 2px; }
.cs-title i { font-style: normal; font-weight: 700; color: var(--text); }
.cs-spacer { flex: 1; }

/* one button shape for the whole window; the topbar's .tb-btn is the reference */
.cs-btn { font: inherit; font-size: 12px; font-weight: 600; height: 26px; padding: 0 11px;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  border-radius: var(--r-sm); border: 1px solid var(--border);
  background: var(--surface); color: var(--text); cursor: pointer;
  transition: background .15s ease, border-color .15s ease, color .15s ease; }
.cs-btn:hover { background: var(--surface-3); border-color: var(--accent-dim); }
.cs-btn:active { background: var(--surface-2); }
.cs-btn.on { background: var(--accent-bg); border-color: var(--accent); color: var(--accent); }
/* the sheet-action cluster reads as one control, like .mode-box in the topbar */
.cs-group { display: inline-flex; align-items: center; gap: 2px; padding: 2px;
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: var(--r-md); }
.cs-btn.cs-ghost { height: 22px; padding: 0 9px; border-color: transparent; background: transparent;
  color: var(--text-dim); }
.cs-btn.cs-ghost:hover { background: var(--surface-3); color: var(--text); border-color: transparent; }
.cs-btn.cs-ghost.on { background: var(--accent-bg); color: var(--accent); border-color: transparent; }
.cs-modebox { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px 3px 9px;
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: var(--r-md);
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-dim); }
.cs-modebox select { font-size: 12px; font-weight: 600; text-transform: none; letter-spacing: 0;
  padding: 2px 4px; border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface-3); color: var(--text); }

.cs-problems { font-size: 11px; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-dirty { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
  color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent);
  border: 1px solid var(--warn); border-radius: 100px; padding: 2px 8px; }
.cs-dirty[hidden] { display: none; }
.cs-btn.cs-savebtn { background: var(--accent); border-color: transparent; color: #fff;
  box-shadow: 0 1px 4px color-mix(in srgb, var(--accent) 35%, transparent); }
.cs-btn.cs-savebtn:hover { filter: brightness(1.08); background: var(--accent); border-color: transparent; }
.cs-btn.cs-savebtn:disabled { opacity: .55; cursor: default; filter: none; box-shadow: none; }
.cs-btn.cs-close:hover { border-color: var(--danger); color: var(--danger); background: var(--surface-2); }
.cs-ok { color: var(--good); }
.cs-bad { color: var(--danger); }

/* one visible focus ring for every control in the window */
.cs-win button:focus-visible, .cs-win select:focus-visible,
.cs-win input:focus-visible, .cs-win label:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 65%, transparent); outline-offset: 1px; }

/* the inspector only ever holds a value, two short fields and a role - it does
   not need the width the left panel does */
.cs-body { flex: 1; display: grid; grid-template-columns: 300px minmax(0,1fr) 200px; min-height: 0; }
@media (min-width: 1500px) { .cs-body { grid-template-columns: 340px minmax(0,1fr) 215px; } }
.cs-side, .cs-inspect { overflow: auto; padding: 10px; font-size: 12px; }
.cs-side { border-right: 1px solid var(--border); background: var(--surface-2); }
.cs-inspect { border-left: 1px solid var(--border); background: var(--surface-2); }
.cs-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }

/* ---- collapsible side sections ---- */
.cs-sec { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--text-dim);
  margin: 14px 0 6px; display: flex; align-items: center; gap: 5px;
  border-bottom: 1px solid var(--border-soft); padding-bottom: 5px; }
.cs-sec:first-child { margin-top: 0; }
.cs-sec-sp { flex: 1; }
.cs-sec-t { font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit;
  background: none; border: 0; padding: 0; cursor: pointer; }
.cs-sec-t:hover, .cs-caret:hover { color: var(--text); }
.cs-caret { width: 14px; height: 14px; flex: none; padding: 0; line-height: 1; font-size: 10px;
  border: 0; background: none; color: var(--text-faint); cursor: pointer;
  transition: transform .15s ease, color .15s ease; }
.cs-caret.open { transform: rotate(90deg); color: var(--text-dim); }
.cs-count { font-size: 9.5px; font-weight: 600; letter-spacing: 0; color: var(--text-faint);
  background: var(--surface-3); border-radius: 100px; padding: 1px 6px; min-width: 17px; text-align: center; }
.cs-secbody[hidden] { display: none; }
.cs-muted { color: var(--text-dim); }
.cs-add, .cs-x { border: 1px solid var(--border); background: var(--surface); color: var(--text-dim);
  border-radius: var(--r-sm); cursor: pointer; line-height: 1; padding: 1px 6px; font-size: 12px;
  transition: color .15s ease, border-color .15s ease; }
.cs-add:hover { color: var(--accent); border-color: var(--accent); }
.cs-x:hover { color: var(--danger); border-color: var(--danger); }

/* formatting toolbar */
.cs-win { --cs-rule: var(--text-dim); }
.cs-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 6px 10px; border-bottom: 1px solid var(--border); background: var(--surface-2); }
/* related tools sit in one pill, the way the topbar groups its overlay toggles */
.cs-tgroup { display: inline-flex; align-items: center; gap: 1px; padding: 2px;
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: var(--r-md); }
.cs-tool { font: inherit; font-size: 12px; min-width: 24px; height: 22px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: var(--r-sm); background: transparent;
  color: var(--text-dim); cursor: pointer; line-height: 1;
  transition: background .15s ease, color .15s ease; }
.cs-tool.cs-tool-w { padding: 0 9px; font-size: 11.5px; font-weight: 600; }
.cs-tool:hover { background: var(--surface-3); color: var(--text); }
.cs-tool.on { background: var(--accent-bg); border-color: var(--accent); color: var(--accent); }
/* drawn toolbar icons: one size for the lot, and transparent to the pointer so
   a click always reports the button rather than the path it landed on */
.cs-ic { width: 14px; height: 14px; display: block; pointer-events: none;
  overflow: visible; }
.cs-toolsep { width: 1px; height: 16px; background: var(--border); margin: 0 5px; }
.cs-swatch { position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 22px; border-radius: var(--r-sm); cursor: pointer;
  font-size: 12px; color: var(--text-dim); overflow: hidden; transition: background .15s ease, color .15s ease; }
.cs-swatch:hover { background: var(--surface-3); color: var(--text); }
.cs-swatch input[type="color"] { position: absolute; inset: auto 0 0 0; height: 5px;
  width: 100%; border: none; padding: 0; background: none; cursor: pointer; }
.cs-numfmt { font: inherit; font-size: 11px; height: 22px; padding: 0 18px 0 7px;
  max-width: 124px; border: 1px solid var(--border); border-radius: var(--r-sm);
  background-color: var(--surface-2); color: var(--text); background-position: right 2px center;
  cursor: pointer; }
.cs-numfmt:hover { border-color: var(--accent-dim); background-color: var(--surface-3); }
/* the dropped-down list is painted by the browser and does NOT inherit the
   toolbar's dimmed colour - state it explicitly or the options are unreadable */
.cs-numfmt option { background-color: var(--surface); color: var(--text); font-size: 12px; }
/* column width, in px, of whatever columns the selection touches */
.cs-colw { font: 500 11px var(--font-mono); width: 48px; height: 22px; padding: 0 4px;
  text-align: right; border: 1px solid var(--border); border-radius: var(--r-sm);
  background-color: var(--surface-2); color: var(--text); outline: none; }
.cs-colw:hover { border-color: var(--accent-dim); background-color: var(--surface-3); }
.cs-colw:focus { border-color: var(--accent); }

.cs-fbar { display: flex; align-items: center; gap: 8px; padding: 6px 10px;
  border-bottom: 1px solid var(--border); background: var(--surface); }
.cs-refbox { font-family: var(--font-mono); font-size: 12px; font-weight: 500; min-width: 62px;
  height: 26px; display: inline-flex; align-items: center; justify-content: center;
  padding: 0 8px; border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface-2); color: var(--accent); }
/* The visible box belongs to the wrapper so the input and the colour mirror
   behind it share one content box exactly - any padding/border difference would
   show up as doubled, offset text. */
.cs-fwrap { position: relative; flex: 1; min-width: 0; height: 26px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-2);
  transition: border-color .15s ease, background .15s ease; }
.cs-fwrap:focus-within { border-color: var(--accent); background: var(--surface);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent); }
#csFormula { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1;
  font-family: var(--font-mono); font-size: 13px; line-height: 24px; padding: 0 9px;
  border: 0; background: transparent; color: var(--text); }
#csFormula:focus { outline: none; }
/* with a mirror underneath, the input keeps the caret but stops drawing glyphs */
#csFormula.cs-mirrored { color: transparent; caret-color: var(--text); }
.cs-fhl { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  font-family: var(--font-mono); font-size: 13px; line-height: 24px; padding: 0 9px;
  white-space: pre; color: var(--text); }
/* the same mirror inside jspreadsheet's own cell editor. The editor input must
   stay the td's FIRST child (jspreadsheet saves td.children[0].value), so the
   mirror is appended after it and kept underneath with z-index instead. */
.cs-grid td.editor { position: relative; }
.cs-grid td.editor > input, .cs-grid td.editor > textarea { position: relative; z-index: 1; }
.cs-fhl-cell { line-height: normal; padding: 0 2px 0 4px; display: flex; align-items: center;
  font-family: inherit; font-size: inherit; inset: 0 auto auto 0; }

/* Reference tokens, coloured to match the outline of the block they point at.
   Colour only - any weight or spacing change here makes the mirror wider than
   the text the input draws underneath, and the two drift apart character by
   character. */
.cs-fhl .cs-tok-0 { color: var(--accent); }
.cs-fhl .cs-tok-1 { color: var(--loss); }
.cs-fhl .cs-tok-2 { color: var(--load); }
.cs-fhl .cs-tok-3 { color: var(--switch); }
.cs-fhl .cs-tok-4 { color: var(--ldo); }
.cs-fhl .cs-tok-5 { color: var(--icr); }
/* an input has no block on the sheet to match, so it gets its own tone */
.cs-fhl .cs-tok-in { color: var(--warn); }
/* Bracket pairs, by nesting depth - the same rule applies as above: colour
   only. A background is safe (it does not move a glyph), and the unmatched
   bracket is the one worth more than a colour. */
.cs-fhl .cs-par-0 { color: var(--cs-par-0); }
.cs-fhl .cs-par-1 { color: var(--cs-par-1); }
.cs-fhl .cs-par-2 { color: var(--cs-par-2); }
.cs-fhl .cs-par-bad { color: var(--danger);
  background: color-mix(in srgb, var(--danger) 22%, transparent); border-radius: 2px; }
/* the pair the caret is resting on. currentColor means the wash is whatever
   tone that depth already wears, so the two halves light up together */
.cs-fhl .cs-par-hit { background: color-mix(in srgb, currentColor 28%, transparent);
  border-radius: 2px; }
/* min-height:0 is what lets a flex child actually shrink and scroll instead of
   growing to fit all its rows and being clipped by the window */
.cs-grid { flex: 1; min-height: 0; overflow: auto; position: relative; }
/* keep the column letters and row numbers in view while scrolling */
.cs-grid > .jexcel_content > table.jexcel > thead > tr > td { position: sticky; top: 0; z-index: 3; }
.cs-grid > .jexcel_content > table.jexcel > tbody > tr > td.jexcel_row { position: sticky; left: 0; z-index: 2; }
.cs-grid > .jexcel_content > table.jexcel > thead > tr > td:first-child { z-index: 4; }
.cs-pickhint { font-size: 11px; color: var(--warn); white-space: nowrap; }
.cs-pickhint[hidden] { display: none; }
/* the field currently waiting for a range picked off the sheet */
.cs-side input.cs-picking { border-color: var(--warn) !important;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--warn) 28%, transparent); }

.cs-grid td.cs-formula { color: var(--accent); }
.cs-grid td.cs-err { color: var(--danger) !important; }
.cs-grid td.cs-named { box-shadow: inset 2px 0 0 var(--accent-dim); }
.cs-grid td.cs-role-input { background: color-mix(in srgb, var(--warn) 14%, transparent) !important; }
.cs-grid td.cs-role-output { background: color-mix(in srgb, var(--good) 16%, transparent) !important; }
.cs-grid td.cs-role-table { background: color-mix(in srgb, var(--accent) 10%, transparent) !important; }
.cs-grid td.cs-role-label { color: var(--text-dim); }
.cs-grid td.cs-pm { outline: 2px solid var(--warn) !important; outline-offset: -2px; }

/* Positioned in VIEWPORT coordinates (see placeAutocomplete). It used to be
   absolutely placed inside .cs-grid - but .cs-grid is the element that scrolls,
   so the list scrolled away with the sheet instead of staying under the caret. */
.cs-auto { position: fixed; z-index: 90; min-width: 220px; max-width: 460px;
  max-height: 168px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--accent); border-radius: var(--r-sm);
  box-shadow: var(--shadow); font-size: 12px; }
.cs-auto[hidden] { display: none; }
.cs-auto-item { padding: 3px 8px; display: flex; gap: 8px; align-items: baseline; cursor: pointer; }
.cs-auto-item.sel, .cs-auto-item:hover { background: var(--accent-bg); }
.cs-auto-k { font-size: 10px; color: var(--text-dim); }
.cs-auto-h { font-size: 10px; color: var(--text-faint); margin-left: auto;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cs-help { border: 1px solid var(--border); background: var(--surface); color: var(--text-dim);
  border-radius: 50%; width: 15px; height: 15px; line-height: 1; font-size: 10px; cursor: pointer; padding: 0; }
.cs-help:hover, .cs-help.on { color: var(--accent); border-color: var(--accent); }
.cs-note { font-size: 11px; line-height: 1.5; color: var(--text-dim); background: var(--surface-3);
  border-left: 2px solid var(--accent-dim); border-radius: var(--r-sm); padding: 6px 8px; margin-bottom: 8px; }
.cs-note code, .cs-guide code { font-family: var(--font-mono); font-size: 10.5px;
  background: var(--surface); padding: 0 3px; border-radius: 3px; }
.cs-hint { font-size: 10.5px; line-height: 1.45; color: var(--text-faint); margin-top: 4px; }

.cs-guide { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px;
  padding: 12px 14px; border-bottom: 1px solid var(--border); background: var(--surface-2); }
.cs-guide[hidden] { display: none; }
.cs-guide h4 { margin: 0 0 3px; font-size: 11px; color: var(--accent); letter-spacing: .03em; }
.cs-guide p { margin: 0; font-size: 11.5px; line-height: 1.55; color: var(--text-dim); }

/* an input is a small card: identity on top, behaviour below. Folded it shrinks
   to a single line - key, label and the preview value you keep poking. */
.cs-card { border: 1px solid var(--border-soft); border-radius: var(--r-sm);
  padding: 4px 6px; margin-bottom: 4px; background: var(--surface);
  transition: border-color .15s ease; }
.cs-card:hover { border-color: var(--border); }
.cs-card.open { border-color: var(--border); padding: 5px 6px; }
.cs-card-hd { display: flex; align-items: center; gap: 5px; cursor: pointer; }
.cs-card-hd .cs-card-k { font-family: var(--font-mono); font-size: 11px; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45%; }
.cs-card-hd .cs-card-l { flex: 1; min-width: 0; font-size: 10.5px; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cs-side .cs-card-hd .cs-card-p { flex: none; width: 72px; }
.cs-card-hd input { cursor: text; }
.cs-card-hd select { cursor: pointer; }
/* the "not saved" warn border is right on one field inside an open card, but a
   folded list stacks eleven of them - keep the signal, drop the shouting */
.cs-card-hd .cs-preview { border-color: color-mix(in srgb, var(--warn) 45%, var(--border)) !important; }
.cs-card-hd .cs-preview:focus { border-color: var(--warn) !important; }
.cs-card-top { display: grid; grid-template-columns: 14px 1fr 1fr 46px 18px; gap: 3px; align-items: center; }
.cs-card-bot { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 5px; margin-top: 4px; }
.cs-card-bot label, .cs-choices { font-size: 9px; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-faint); display: block; }
.cs-choices { margin-top: 4px; }
.cs-card-bot label input, .cs-card-bot label select, .cs-choices input { margin-top: 1px; }

.cs-tbl-row, .cs-out-row, .cs-hdr { display: grid; gap: 3px; align-items: center; margin-bottom: 3px; }
.cs-tbl-row { grid-template-columns: 1fr 90px 20px; }
/* An output row is eight controls and the panel is 280 px wide, so it is two
   lines: what the cell is, then where its number goes. Crammed onto one line
   the flexible columns were squeezed to a few pixels each - the key field had
   no room for a single character - and the row overflowed the panel, which cut
   the last controls off entirely. */
.cs-out-row, .cs-hdr-out { grid-template-columns: minmax(0,1fr) minmax(0,1fr) 46px; }
.cs-out-row { padding-bottom: 5px; border-bottom: 1px solid var(--border); }
.cs-out-row:last-child { border-bottom: none; }
.cs-out-more { grid-column: 1 / -1; display: grid; gap: 3px; align-items: center;
  grid-template-columns: 52px 62px minmax(0,1fr) auto 20px; }
/* The tick is a control, not a field: it does not want the full-width rule the
   inputs and selects in this panel share, and it carries its own word so the
   second line needs no heading of its own. */
.cs-tick-lbl { display: inline-flex; align-items: center; gap: 3px; white-space: nowrap;
  font-size: 10px; color: var(--text-dim); cursor: pointer; }
.cs-tick-lbl.cs-off { opacity: .4; cursor: default; }
.cs-out-row .cs-tick { width: auto; margin: 0; }
.cs-hdr { font-size: 9px; color: var(--text-faint); text-transform: uppercase; }
.cs-side input, .cs-side select, .cs-inspect input, .cs-inspect select {
  font: inherit; font-size: 11px; padding: 2px 4px; min-width: 0; width: 100%;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); color: var(--text); }
.cs-side input.cs-key { font-family: var(--font-mono); }
.cs-preview { border-color: var(--warn) !important; }
.cs-read { margin-top: 8px; }
.cs-read-row { display: flex; gap: 6px; align-items: baseline; padding: 2px 0;
  border-top: 1px dashed var(--border-soft); font-size: 11px; }
.cs-read-row b { font-family: var(--font-mono); }
.cs-lbl { display: block; margin: 6px 0; font-size: 10px; color: var(--text-dim); }
.cs-kv { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; }
.cs-kv b { font-family: var(--font-mono); }
.cs-trace { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim);
  background: var(--surface-3); border-radius: var(--r-sm); padding: 5px 6px; margin: 6px 0;
  word-break: break-all; }
.cs-deps { display: flex; flex-wrap: wrap; gap: 4px; }
.cs-dep { font-family: var(--font-mono); font-size: 10px; padding: 2px 5px; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); color: var(--text-dim);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }
.cs-dep:hover { border-color: var(--accent); color: var(--accent); }
/* a dependency that is a whole lookup block, not one cell */
.cs-dep.cs-dep-blk { border-style: dashed; }
/* a declared input: a real dependency, but there is no address to jump to */
.cs-dep.cs-dep-in { cursor: default; color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 45%, var(--border));
  background: color-mix(in srgb, var(--warn) 8%, var(--surface)); }

/* ---- a pack: cells joined into one source (js/render.js) ----
   Dashed, and in the source colour: it is not a rail, it is two cells bolted
   together. The badge says which way - S stacks the volts, P adds the hours. */
.packlink { fill: none; stroke: var(--k-source); stroke-width: 2;
  stroke-dasharray: 6 4; opacity: .85; }
.packbadge { fill: var(--surface); stroke: var(--k-source); stroke-width: 1.5; }
.packbadge-t { fill: var(--k-source); font: 700 10px var(--font-sans);
  text-anchor: middle; pointer-events: none; }

/* ---- Excel-style outlines on the cells a formula reads ----
   Drawn on a pseudo-element rather than the cell's own border/box-shadow: a cell
   may already carry user borders as inline !important declarations, which would
   otherwise win. Only the outer edges of a range get a side, so a block reads as
   one frame. */
.cs-grid td.cs-ref { position: relative; }
.cs-grid td.cs-ref::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 2;
  border: 0 solid var(--cs-ref-c, var(--accent)); }
.cs-grid td.cs-ref-t::after { border-top-width: 2px; }
.cs-grid td.cs-ref-b::after { border-bottom-width: 2px; }
.cs-grid td.cs-ref-l::after { border-left-width: 2px; }
.cs-grid td.cs-ref-r::after { border-right-width: 2px; }
/* The grab square, on the corner the eye already goes to. It hangs half
   outside the cell so it can be caught without landing inside the block. */
.cs-grid td.cs-ref-h::before {
  content: ""; position: absolute; z-index: 3; right: -3px; bottom: -3px;
  width: 6px; height: 6px; border-radius: 1px; pointer-events: none;
  background: var(--cs-ref-c, var(--accent));
  box-shadow: 0 0 0 1px var(--surface);
}
.cs-grid td.cs-ref-0 { --cs-ref-c: var(--accent); }
.cs-grid td.cs-ref-1 { --cs-ref-c: var(--loss); }
.cs-grid td.cs-ref-2 { --cs-ref-c: var(--load); }
.cs-grid td.cs-ref-3 { --cs-ref-c: var(--switch); }
.cs-grid td.cs-ref-4 { --cs-ref-c: var(--ldo); }
.cs-grid td.cs-ref-5 { --cs-ref-c: var(--icr); }

/* ---- modes modal ---- */
.dialog.small { width: 460px; height: auto; max-height: 80vh; }
.modes-body { padding: 12px; overflow-y: auto; }
.mode-row { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; padding: 5px 0; font-size: 12px; }
.mode-row .mname { font-weight: 600; cursor: pointer; color: var(--text); }
.mode-row .mdel { border: none; background: none; color: var(--text-dim); cursor: pointer; font-size: 14px; }
.mode-row .mdel:hover { color: var(--danger); }
.modes-body .addbtn { background: var(--surface-2); border: 1px solid var(--border); color: var(--text); padding: 4px 10px; border-radius: 5px; cursor: pointer; }
.modes-body .addbtn:hover { background: var(--surface-3); }
#modeAdd { margin-top: 10px; }
#canvas {
  flex: 1; position: relative; overflow: hidden; cursor: grab;
  user-select: none; -webkit-user-select: none;
  background: transparent;
}
#canvas.grabbing { cursor: grabbing; }
#canvas.dragging-node { overflow: visible; z-index: 100; }
#canvas.dragging-node #svg { overflow: visible; }
#svg { width: 100%; height: 100%; display: block; }

/* ---- left palette ---- */
#palette { width: 158px; border-right: 1px solid var(--border); background: var(--panel); display: flex; flex-direction: column; min-height: 0; }
.pal-head { display: flex; flex-direction: column; gap: 6px; padding: 8px; border-bottom: 1px solid var(--border); font-size: 11px; font-weight: 700; color: var(--text-dim); }
#catFilter { font-size: 11px; padding: 3px 5px; border: 1px solid var(--border); border-radius: 5px; background: var(--surface-2); color: var(--text); }
#paletteList { flex: 1; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 5px; }
.pal-item { display: flex; align-items: center; justify-content: space-between; gap: 6px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); cursor: grab; font-size: 11px; }
.pal-item:hover { border-color: var(--accent); background: var(--accent-bg); }
.pal-item .cid { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pal-item .badge, #panel .badge { font: 700 8.5px var(--font-mono); padding: 1px 5px; border-radius: 4px; background: var(--surface-3); color: var(--text); }
/* The kind badge carries the family colour, so what you drag onto the canvas
   is already the colour it will be there. */
.pal-item .badge { background: color-mix(in srgb, var(--k) 22%, var(--surface-3)); }
.pal-note-btn { margin: 8px 8px 0 8px; margin-top: auto; border-style: dashed; }

/* ---- where a palette entry comes from ---- */
/* The mark is one letter, not a word: the panel is ~158px and a word pushed
   every library row onto a second line. The legend below the filter carries the
   meaning once, instead of once per row. */
.pal-scope-filter { width: calc(100% - 16px); margin: 0 8px 6px; font-size: 11px; }
.pal-legend { padding: 0 8px 8px; font-size: 9px; line-height: 1.5; color: var(--text-faint); }
.pal-legend b { font: 700 8.5px var(--font-mono); color: var(--muted); }
/* Already in the project: dragging it adds a node and nothing else. */
.pal-item.s-project { border-left: 2px solid var(--accent); }
.pal-item .scope {
  font: 700 8.5px var(--font-mono); padding: 1px 4px; border-radius: 4px; flex: none;
}
.pal-item .scope.global { background: var(--surface-3); color: var(--muted); }
.pal-item .scope.mine { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent); }
.pal-hint { padding: 6px 8px; font-size: 9.5px; color: var(--text-faint); border-top: 1px solid var(--border); }

#palette.delete-hover {
  background: color-mix(in srgb, var(--danger) 8%, var(--surface));
  border-right-color: var(--danger);
  border-right-width: 2px;
}
#palette.delete-hover .pal-head {
  color: var(--danger);
}
#palette.delete-hover .pal-hint {
  color: var(--danger);
  font-weight: 700;
  text-transform: uppercase;
  font-size: 0;
}
#palette.delete-hover .pal-hint::after {
  content: "Drop here to delete";
  font-size: 11px;
}
#palette.delete-hover .pal-item {
  opacity: 0.3;
}

/* ---- loads on the results page ---- */
.ld-block { margin-top: 14px; }
.ld-block h3 { font-size: 11px; letter-spacing: .08em; color: var(--muted); margin: 0 0 8px; }
.ld-card {
  border: 1px solid var(--border); border-radius: 7px; background: var(--surface-2);
  padding: 8px 10px; margin-bottom: 8px; font-size: 11px;
}
.ld-card.off { opacity: .55; }
.ld-head {
  display: flex; align-items: center; gap: 7px; width: 100%;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  color: inherit; font: inherit; text-align: left;
}
.ld-head:hover .cid { color: var(--accent); }
.ld-caret { color: var(--muted); font-size: 9px; transition: transform .12s ease; flex: none; }
.ld-card.folded .ld-caret { transform: rotate(-90deg); }
.ld-card.folded .ld-body { display: none; }
.ld-card:not(.folded) .ld-head { margin-bottom: 6px; }
.ld-head .cid { font-weight: 600; }
.ld-head .lref { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ld-head .ld-p { margin-left: auto; font-family: var(--font-mono); color: var(--load); white-space: nowrap; }
.ld-row { display: flex; align-items: center; gap: 6px; margin: 3px 0; color: var(--muted); }
.ld-row > span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ld-row.read { color: var(--text); }
.ld-row.read b { font-family: var(--font-mono); }
/* set for this mode only, rather than inherited from the component */
.ld-row.ovr > span { color: var(--accent); }
.ld-in {
  width: 96px; font-size: 11px; padding: 3px 5px; border-radius: 5px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
}
.ld-chk { display: flex; align-items: center; gap: 5px; color: var(--muted); margin: 3px 0; }
.ld-note { font-size: 10px; color: var(--text-faint); margin: 4px 0 2px; }
/* the modes hold different values and this field is about to level them */
.ld-flat { color: var(--warn); font-family: var(--font-mono); cursor: help; }
/* Which modes take their length from the execution time on this row. */
.ld-follow { font-style: normal; font-size: 10px; color: var(--accent); }
/* The battery page carries the same block, in its own column beside the
   profile rather than under it - the loads are what the life estimate is made
   of, so they belong in view while the times are being set. */
.bat-layout { display: flex; gap: 24px; align-items: flex-start; }
.bat-main { flex: 1; min-width: 0; }
.bat-side { flex: 0 0 340px; }
.bat-side .ld-block { margin-top: 0; }

/* ---- figures ----
   The cards every chart in the application sits in - the battery page's and the
   results dashboard's alike. The class names say "bat" for where they were
   written, not for where they are used.

   Thin marks, hairline solid grid, text in text tokens only: the fills are the
   only thing on the picture wearing a series colour. */
/* One grid, not a row that wraps. Wrapping gave every row whatever cards fit
   and stretched the leftovers, so four figures came out three-and-one and no
   two of them were the same size; these are read against each other, and that
   made it a comparison of shapes. Equal columns, and each row as tall as its
   tallest card, so the edges line up. */
.bat-plots { display: grid; gap: 16px; align-items: stretch;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 430px), 1fr)); }
/* A note about the whole set - "no discharge figure, and here is why" - is not
   a card and does not take a column. */
.bat-plots > .bp-note { grid-column: 1 / -1; margin: 0; }
.bat-plot { min-width: 0; margin: 0;
  border: 1px solid var(--border); border-radius: 9px; background: var(--surface);
  padding: 12px 14px 10px;
  /* What an axis break paints over the data it removes. It has to be whatever
     is behind the figure, and that is not the same colour in the enlarged view
     - see .fz-stage.bat-plot, which drops the card. */
  --bp-paper: var(--surface); }
.bat-plot .bp-cut { fill: var(--bp-paper); }
.bat-plot .bp-cut-line { stroke: var(--border); stroke-width: 1.5; }
.bat-plot figcaption { font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-dim); margin-bottom: 8px;
  display: flex; align-items: center; gap: 8px; }
.bat-plot figcaption .fig-zoom { margin-left: auto; }
.bp-note { font-size: 11px; color: var(--text-faint); margin: 6px 0 0; line-height: 1.4; }
.bat-plot .bp-grid { stroke: var(--border); stroke-width: 1; }
.bat-plot .bp-minor { opacity: .45; }
/* The 2..9 of each decade: present enough to shape the axis, faint enough that
   the staircase drawn over them is still the thing you see. */
.bat-plot .bp-log { opacity: .4; }   /* about half the weight of the decade
   line above it, which is what tells the two apart at a glance: the named
   lines are the ones you read a number off. */
.bat-plot .bp-axis { stroke: var(--border); stroke-width: 1; }
.bat-plot .bp-lead { stroke: var(--border); stroke-width: 1; }
/* the ring is the surface, so a dot stays legible where it lands on a gridline */
.bat-plot .bp-dot { stroke: var(--surface); stroke-width: 2; }
.bat-plot text { font-family: var(--font-sans); }
.bat-plot .bp-cat { font-size: 10.5px; fill: var(--text); }
.bat-plot .bp-tick { font-size: 9.5px; fill: var(--text-faint);
  font-family: var(--font-mono); }
.bat-plot .bp-val { font-size: 9.5px; fill: var(--text-dim); font-family: var(--font-mono); }
.bat-plot .bp-off { font-size: 10px; fill: var(--text-faint); font-style: italic; }
.bat-plot .bp-leg { font-size: 10.5px; fill: var(--text-dim); }

/* ---- the energy ring answers a pointer ----
   A slice and its legend row are one part, so they are one group: point at
   either and both light, which is what makes a twenty-part ring readable
   without matching colours by eye. The lit slice also lifts a few units out
   along its own mid-angle - the ring's own way of saying "this one".
   The dim tier that tells the second nine hues from the first lives on
   fill-opacity, so opacity here is free to mean hover and only hover. */
.bat-plot .bp-seg, .bat-plot .bp-leg-sw, .bat-plot .bp-item text {
  transition: opacity .12s ease, transform .12s ease;
}
.bat-plot .bp-leg-bg { fill: transparent; transition: fill .12s ease; }
/* The group's hit area is the union of its items, so this is exactly "the
   pointer is on some part" - the hole, the total and the paper are not. */
.bat-plot .bp-items:hover .bp-seg,
.bat-plot .bp-items:hover .bp-leg-sw,
.bat-plot .bp-items:hover text { opacity: .32; }
.bat-plot .bp-items .bp-item:hover .bp-seg,
.bat-plot .bp-items .bp-item:hover .bp-leg-sw,
.bat-plot .bp-items .bp-item:hover text { opacity: 1; }
.bat-plot .bp-items .bp-item:hover .bp-seg { transform: translate(var(--ox), var(--oy)); }
.bat-plot .bp-items .bp-item:hover .bp-leg-bg { fill: var(--surface-3); }

@media (prefers-reduced-motion: reduce) {
  .bat-plot .bp-seg, .bat-plot .bp-leg-sw,
  .bat-plot .bp-item text, .bat-plot .bp-leg-bg { transition: none; }
  .bat-plot .bp-items .bp-item:hover .bp-seg { transform: none; }
}
/* The mode a slice belongs to, over the slice's own name. */
.bat-plot .bp-sub { font-size: 9.5px; fill: var(--text-dim); }
/* the swing the cell makes every period, between the heaviest mode and the
   lightest: a band, because the two edges are not two long runs */
.bat-plot .bp-band { fill: var(--text); opacity: .085; }
.bat-plot .bp-line { stroke: var(--dv-1); stroke-width: 2; stroke-linejoin: round;
  stroke-linecap: round; }
/* the one number the whole ring adds up to, in the hole it leaves */
.bat-plot .bp-hero { font-size: 19px; font-weight: 600; fill: var(--text); }

/* ---- marks the results figures add ----
   A name written ON a fill is the one place in these figures where text sits on
   a series colour, so it carries its own contrast rather than a text token. */
.bat-plot .rp-tm-name { font-size: 11px; font-weight: 600; fill: #fff;
  paint-order: stroke; stroke: rgba(0,0,0,.35); stroke-width: 2.5px; }
.bat-plot .rp-tm-val { font-size: 10px; fill: #fff; opacity: .92;
  font-family: var(--font-mono);
  paint-order: stroke; stroke: rgba(0,0,0,.35); stroke-width: 2.5px; }
.bat-plot .rp-cell { stroke: var(--surface); stroke-width: 1; }
/* The empty part of a headroom bar: what is left, drawn rather than implied. */
.bat-plot .rp-track { fill: var(--bar-track); }
/* A threshold is not data - dashed, so it never reads as another bar. */
.bat-plot .rp-rule { stroke: var(--text-faint); stroke-width: 1;
  stroke-dasharray: 3 3; }
/* narrow window: the column has nowhere to go, so it goes underneath - and
   under the profile rather than over it, since the profile is what the page is
   for. */
@media (max-width: 1100px) {
  .bat-layout { flex-direction: column; }
  .bat-side { flex: 1 1 auto; align-self: stretch; order: 1; }
}

/* A curve editor that is still filled in but no longer feeds the solver. */
.eff-unused { opacity: .45; }
.eff-unused:hover { opacity: .8; }

/* ---- node editor (right panel) ---- */
.ed { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.ed > label { display: flex; flex-direction: column; gap: 4px; color: var(--muted); font-size: 11.5px; font-weight: 500; }
.ed input, .ed select { font-size: 12px; padding: 6px 8px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-3); color: var(--text); transition: all 0.2s; }
.ed input:focus, .ed select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(47, 141, 158, 0.15); }
.ed .chk { flex-direction: row; align-items: center; gap: 6px; color: var(--ink); }
.ed .chk.small { justify-content: space-between; }
.ed .chk.small input { width: 74px; }
.ed-en { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; margin-bottom: 4px; }
.ed-loads { display: flex; flex-direction: column; gap: 6px; }
.rail-row { display: grid; grid-template-columns: 85px 1fr; gap: 8px; align-items: center; color: var(--text); font-size: 11.5px; }
.rail-row span { font-weight: 500; color: var(--text); }

/* Premium Panel Aesthetics */
.ed .sec, .res-sec { color: var(--accent); font-weight: 600; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; margin-top: 18px; margin-bottom: 8px; border-bottom: 1px solid var(--border-soft); padding-bottom: 4px; }
.ed .hint { font-size: 10.5px; color: var(--text-dim); margin-bottom: 10px; }
#panelBody h3 { font-size: 16px; font-weight: 600; color: var(--accent); display: flex; align-items: center; gap: 8px; margin-top: 0; margin-bottom: 16px; }
#panelBody h3 .badge { font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 4px; background: var(--surface-3); color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
#panelBody .grid { display: grid; grid-template-columns: 1fr auto; gap: 6px 12px; font-size: 11.5px; align-items: center; background: var(--surface-2); padding: 12px; border-radius: var(--r-md); border: 1px solid var(--border-soft); margin-bottom: 16px; }
#panelBody .grid .k { color: var(--muted); }
#panelBody .grid .v { font-family: var(--font-mono); color: var(--num); text-align: right; font-weight: 500; }
.pin-row { display: flex; justify-content: space-between; font-size: 11.5px; padding: 6px 0; border-bottom: 1px dashed var(--border-soft); }
.pin-row:last-child { border-bottom: none; }
.pin-name { color: var(--text); }
.pin-val { font-family: var(--font-mono); color: var(--num); font-weight: 500; }
.ed .form-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 24px; border-top: none; padding-top: 0; }
.ed .form-actions .save { grid-column: 1 / -1; font-size: 13px; padding: 10px; box-shadow: 0 2px 8px rgba(47,141,158,0.25); }
.ed .form-actions .save:hover { filter: brightness(1.1); box-shadow: 0 4px 12px rgba(47,141,158,0.35); }
.ed .form-actions .cancel, .ed .form-actions .del { width: 100%; margin: 0; padding: 8px; font-size: 12px; text-align: center; background: var(--surface-2); }
.ed .form-actions .cancel:hover, .ed .form-actions .del:hover { background: var(--surface-3); }

/* ---- node blocks ---- */
.node { cursor: pointer; }
.node .box { fill: var(--surface); stroke: var(--border); stroke-width: 1.4; }
.node.selected .box { stroke: var(--accent); stroke-width: 2.6; }
.node.err .box { stroke: var(--danger); stroke-width: 2.2; }
.node.warn .box { stroke: var(--warn); stroke-width: 2.2; }
.node.inactive .box { opacity: 0.5; stroke-dasharray: 4 3; }
.node.overridden { opacity: 0.65; }
.node.overridden .box { stroke: var(--text-dim); stroke-dasharray: 4 3; }
.node.overridden text, .node.overridden .val, .node.overridden .htext, .node.overridden .zeroval { fill: var(--text-dim); }
.load-item.overridden { opacity: 0.65; filter: grayscale(0.6); }
/* ---- .node kinds: what a part IS, in colour ----
 *
 * Five families, because five is what a canvas can be read by at a glance: a
 * source, something that switches, something that regulates linearly, a load,
 * something that emits. Series elements - a switch, a fuse, a sense resistor, a
 * MUX - get no hue at all, which is the honest reading: nothing is converted as
 * the rail passes through one, and one colour fewer is one thing fewer to
 * decode.
 *
 * The five hues sit 72 degrees apart on the OKLCH ring, rotated so that each
 * family lands on the colour it already meant in this app. That even spacing is
 * not decoration: inside the dark theme's lightness band, two hues 45 degrees
 * apart cannot be told apart reliably at any lightness. Both sets clear the
 * data-viz checks - lightness band, chroma floor, colour-vision separation and
 * 3:1 against the surface - measured at "any pair", since any two kinds can end
 * up side by side.
 *
 * Restraint is in WHERE the colour lands, not in how washed out it is: the hue
 * tints the header band and draws the hairline under it, and the card stays the
 * same neutral surface as everything else. The box outline is left alone
 * entirely, because that is where status lives - selected, error, warning,
 * inactive - and status must always win over identity. */
.node, .pal-item { --k: var(--border); }
.node.source, .pal-item.k-source { --k: var(--k-source); }
.node.buck, .node.boost, .node.buck_boost, .node.pmic,
.pal-item.k-buck, .pal-item.k-boost, .pal-item.k-buck_boost, .pal-item.k-pmic { --k: var(--k-conv); }
.node.ldo, .pal-item.k-ldo { --k: var(--k-lin); }
.node.load, .pal-item.k-load { --k: var(--k-load); }
.node.led, .node.ir_led, .node.led_driver, .node.amp,
.pal-item.k-led, .pal-item.k-ir_led, .pal-item.k-led_driver, .pal-item.k-amp { --k: var(--k-emit); }

.node .hdr { fill: color-mix(in srgb, var(--k) 24%, var(--surface-2)); }
.node .hrule { stroke: var(--k); stroke-width: 1.5; }
.node .box { fill: color-mix(in srgb, var(--k) 5%, var(--surface)); }
.node .htext { font: 700 11px var(--font-sans); fill: var(--text); }
.node .hsub { font: 500 9px var(--font-mono); fill: var(--text); }
.node .lab { font: 8.5px var(--font-mono); fill: var(--text-faint); }
.node .val { font: 9px var(--font-mono); fill: var(--text); }
.node .div { stroke: var(--border-soft); stroke-width: 1; }
.node.zero .box { fill: var(--surface-3); }
.node .zerodes { font: 700 10px var(--font-mono); fill: var(--text-dim); }
.node .zeroval { font: 700 13px var(--font-mono); fill: var(--text); }
.node .raillabel { font: 700 9px var(--font-mono); fill: var(--accent); }
.node .gname { font: 9px var(--font-mono); fill: var(--text); }
.node .gval { font: 9px var(--font-mono); fill: var(--text-dim); }
/* connection ports for mouse wiring */
.port { fill: var(--accent); opacity: 0; transition: opacity .1s; }
.port-out { cursor: crosshair; }
.node:hover .port { opacity: 0.55; }
.node:hover .port-out { opacity: 0.85; }
.node.err .val.io { fill: var(--danger); font-weight: 700; }
.node .ds { font: 9px var(--font-sans); fill: var(--text-dim); }

.wire { fill: none; stroke: var(--muted); stroke-width: 1.8; }
.wire-voltage { font-family: var(--font-mono); font-size: 10px; fill: var(--text); opacity: 0.7; text-anchor: middle; pointer-events: none; user-select: none; }
.wire-voltage.err { fill: var(--danger); opacity: 1; font-weight: 500; }
.junction { fill: var(--muted); }

/* ---- .wire bands: what a wire CARRIES ----
 *
 * Six bands - core, 1.8, 3.3, the cell itself, 5, and everything above - so a
 * rail can be followed across a crowded canvas without reading the label on
 * every leg. A boundary sits between 3.3 V and a lithium cell on purpose: in a
 * battery-powered tree those two are the pair most often side by side, and a
 * band that swallowed both would be the one worth having. The bands are
 * absolute, not ranked: 3.3 V is the third step whatever else is in the tree,
 * so adding a rail never repaints the others.
 *
 * A colour per band, walking the wiring code as the voltage climbs: blue, teal,
 * olive, amber, crimson, and violet kept for the high-voltage end. The strict
 * reading of the method would make this a one-hue ramp - the bands are an order,
 * not a set of names - and the ramp is the version to go back to if the canvas
 * ever feels loud; what buys this one its place is that six shades of one hue
 * are hard to tell apart on a 1.8 px line, and a wire that has to be traced
 * across a sheet is exactly the mark that needs telling apart.
 *
 * Checked as a categorical set at "any pair", against the canvas rather than a
 * card: lightness band, chroma floor, 3:1 contrast, and colour-vision
 * separation in the 6-8 band - legal because it is never the only cue, since
 * the rail voltage is printed on every trunk.
 *
 * A rail whose voltage is not known keeps the neutral stroke, and a leg that is
 * switched off or in error keeps the status colours below: state beats rail. */
.wire.w1 { stroke: var(--w-1); }
.wire.w2 { stroke: var(--w-2); }
.wire.w3 { stroke: var(--w-3); }
.wire.w4 { stroke: var(--w-4); }
.wire.w5 { stroke: var(--w-5); }
.wire.w6 { stroke: var(--w-6); }
.junction.w1 { fill: var(--w-1); }
.junction.w2 { fill: var(--w-2); }
.junction.w3 { fill: var(--w-3); }
.junction.w4 { fill: var(--w-4); }
.junction.w5 { fill: var(--w-5); }
.junction.w6 { fill: var(--w-6); }

.wire.err { stroke: var(--danger); stroke-width: 2.2; }
.wire.inactive { stroke: var(--border); stroke-dasharray: 4 3; }
.wire.wire-temp { stroke: var(--accent); stroke-width: 2; stroke-dasharray: 5 3; pointer-events: none; }
.wirehit { fill: none; stroke: transparent; stroke-width: 12; cursor: pointer; pointer-events: stroke; }

/* ---- side panel ---- */
#panel {
  width: 250px; border-left: 1px solid var(--line); background: var(--panel);
  padding: 12px; overflow-y: auto; font-size: 12px;
}
#panel h3 { margin: 0 0 2px; font-size: 14px; color: var(--accent); }
#panel .sub { color: var(--muted); font-size: 11px; margin-bottom: 10px; }
#panel .grid { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; font-family: var(--mono); }
#panel .grid .k { color: var(--muted); }
#panel .grid .v { text-align: right; font-weight: 600; }
#panel .sec { margin: 12px 0 5px; font-size: 10px; font-weight: 700; letter-spacing: .05em; color: var(--muted); }
.muted { color: var(--muted); }

/* ---- findings bar ---- */
#findings {
  border-top: 1px solid var(--border-soft); background: var(--surface-2);
  padding: 6px 14px; font-size: 12px; font-family: var(--font-mono);
  display: flex; gap: 18px; align-items: center; flex-wrap: wrap; min-height: 30px;
}
#findings .err { color: var(--danger); font-weight: 700; }
#findings .warn { color: var(--warn); font-weight: 700; }
#findings .ok { color: var(--good); }
#findings .finding { margin-right: 18px; }
#findings .f-error { color: var(--danger); }
#findings .f-error b { font-weight: 700; }
#findings .f-warn { color: var(--warn); }

/* ---- inline editor + toast (native prompt/alert blocked in preview) ---- */
.inline-edit { position: fixed; z-index: 200; font-size: 12px; padding: 3px 6px; border: 1.5px solid var(--accent); border-radius: 5px; background: var(--surface-3); color: var(--text); box-shadow: 0 4px 14px rgba(0,0,0,.2); }
#toast { position: fixed; left: 50%; top: 20px; transform: translateX(-50%) translateY(-10px); background: #2c2c2a; color: #fff; font-size: 12px; padding: 8px 14px; border-radius: 8px; opacity: 0; pointer-events: none; transition: opacity .15s, transform .15s; z-index: 300; max-width: 70vw; }
#toast.show { opacity: 0.95; transform: translateX(-50%) translateY(0); }

/* ---- right-click context menu ---- */
/* Above the modals as well as above the canvas: the project list opens one of
   these, and an overlay at z-index 1000 would otherwise swallow it. */
.ctxmenu { position: fixed; z-index: 1200; background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; box-shadow: var(--shadow); padding: 4px; min-width: 170px; }
.ctxitem { padding: 6px 10px; font-size: 12px; border-radius: 5px; cursor: pointer; color: var(--ink); white-space: nowrap; }
.ctxitem:hover { background: var(--accent-bg, #eeedfe); }
.ctxitem.danger { color: var(--danger); }
/* Mixed from the token, or the hover is a pink smear on a dark menu. */
.ctxitem.danger:hover { background: color-mix(in srgb, var(--danger) 16%, transparent); }

/* ---- modal / builder ---- */
.overlay {
  position: fixed; inset: 0; background: rgba(30, 28, 24, 0.4);
  display: flex; align-items: center; justify-content: center; z-index: 50;
}
.overlay[hidden] { display: none; }
.dialog {
  width: 1300px; max-width: 96vw; height: 820px; max-height: 94vh;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  display: flex; flex-direction: column; overflow: hidden; box-shadow: var(--shadow);
}
.dialog-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 14px; border-bottom: 1px solid var(--border); background: var(--surface-2); font-size: 14px;
}
.dialog-head .x { border: none; background: none; font-size: 16px; cursor: pointer; color: var(--muted); }
.dialog-body { flex: 1; display: grid; grid-template-columns: 240px 1fr; min-height: 0; }
.lib-list { border-right: 1px solid var(--border); background: var(--panel); padding: 10px; overflow-y: auto; }
.new-btn { width: 100%; padding: 7px; margin-bottom: 10px; cursor: pointer; font-size: 12px;
  border: 1px solid var(--border-soft); color: var(--text); background: var(--surface-2); border-radius: 7px; font-weight: 600; transition: background .2s, border-color .2s; }
.new-btn:hover { background: var(--surface-3); border-color: var(--border); }
.comp-item { padding: 7px 9px; border: 1px solid var(--border); border-radius: 7px; background: var(--surface);
  margin-bottom: 6px; cursor: pointer; font-size: 12px; }
.comp-item:hover { border-color: var(--accent); }
.comp-item.active { border-color: var(--accent); background: var(--accent-bg); }
.comp-item .cid { font-weight: 700; color: var(--text); }
.comp-item .badge { font: 700 9px var(--font-mono); padding: 1px 6px; border-radius: 4px; background: var(--surface-3); color: var(--text); margin-left: 6px; }
.comp-item .cdesc { color: var(--text-dim); font-size: 10.5px; margin-top: 2px; }

.lib-form { padding: 16px 18px; overflow-y: auto; }

/* ---- .ptc exchange ---- */
.lib-io { display: flex; gap: 6px; margin-bottom: 10px; }
.lib-io button {
  flex: 1; padding: 6px; font-size: 12px; cursor: pointer; border-radius: 7px;
  border: 1px solid var(--border-soft); background: var(--surface-2); color: var(--text);
}
.lib-io button:hover { background: var(--surface-3); border-color: var(--border); }
/* Above the library modal they are launched from, below the auth one. */
#importModal, #copiesModal { z-index: 60; }
.tb-btn.has-updates { color: var(--warn); border-color: var(--warn); }
.imp-state.muted { color: var(--muted); }
.imp-note .warn { color: var(--warn); }
.dialog-import { width: 900px; height: auto; max-height: 88vh; margin-top: 5vh; }
.import-body { padding: 14px 16px; overflow-y: auto; font-size: 12px; }
.imp-summary { margin-bottom: 12px; color: var(--text); }
.imp-row {
  border: 1px solid var(--border); border-radius: 7px; padding: 9px 11px;
  margin-bottom: 8px; background: var(--surface-2);
}
.imp-head { display: flex; align-items: center; gap: 9px; }
.imp-head .cid { font-weight: 600; }
.imp-act-wrap { margin-left: auto; display: flex; gap: 6px; align-items: center; }
.imp-act-wrap select, .imp-act-wrap input {
  font-size: 12px; padding: 4px 6px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
}
.imp-state { font-size: 11px; }
.imp-state.ok { color: var(--good); }
.imp-state.warn { color: var(--warn); }
.imp-state.bad { color: var(--danger); }
/* The colour the digitiser lifted off the plot: a row is matched to the line
   it came from by looking, without reading anybody's name. */
.imp-head .sw { width: 12px; height: 12px; border-radius: 3px; flex: none; border: 1px solid var(--border); }
.imp-foot { display: flex; gap: 8px; justify-content: flex-end; margin-top: 14px; }
.imp-note { font-size: 11px; color: var(--muted); margin-top: 4px; }
.imp-note.warn { color: var(--warn); }
.imp-note.bad { color: var(--danger); }
.imp-map {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 6px; margin-top: 8px;
}
.imp-map label { display: flex; align-items: center; gap: 6px; color: var(--muted); }
.imp-map select {
  flex: 1; font-size: 11px; padding: 3px 5px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
}
#builder { display: flex; flex-direction: column; gap: 10px; font-size: 12px; }
#builder label { display: flex; flex-direction: column; gap: 3px; color: var(--muted); }
#builder input:not([type="checkbox"]):not([type="radio"]), #builder select, #builder textarea {
  font-size: 12px; padding: 5px 7px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-2); color: var(--text);
}
#builder .row { display: flex; gap: 10px; }
#builder .row > label { flex: 1; }
#builder .sec { color: var(--muted); font-weight: 700; font-size: 10px; letter-spacing: .05em; margin-top: 6px; }
/* A section that folds says so: the whole strip is the handle, and the count
   on the right is what is behind it while it is shut. */
/* A field the calculation sheet has taken over: still yours to type in, and
   plainly not what the solve is reading. */
#builder .pins-tbl input.by-sheet { opacity: .45; }
#builder .pins-tbl input.by-sheet:focus { opacity: 1; }
#builder .muted .by-sheet { opacity: .45; }
#builder .sec-row { display: flex; align-items: center; gap: 6px; }
#builder .sec-fold { cursor: pointer; user-select: none; }
#builder .sec-fold:hover { color: var(--text); }
#builder .fold-caret { font-size: 9px; line-height: 1; }
#builder .sec-sum { margin-left: auto; font-weight: 400; letter-spacing: 0; color: var(--text-dim); }
/* The row that says how the discharge curve below is stated: which axis, and
   whether there is one curve or one per load current. */
#builder .curve-opts { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 2px 0 4px; }
/* The state-of-charge slider in the node panel. The track gets the whole width
   of the panel, with its label and readout on the line above: a slider is only
   as fine as it is long, and this one is meant to be dragged. */
/* A section heading with a control on its right - the scope switch over
   the calculation-sheet inputs. */
.ed .sec.sec-row { display: flex; align-items: center; justify-content: space-between;
  gap: 8px; }
.ed .sec.sec-row .tb-btn.ov { text-transform: none; letter-spacing: 0;
  max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  display: block; }
.ed .soc-box { display: flex; flex-direction: column; gap: 4px; }
.ed .soc-head { display: flex; align-items: baseline; justify-content: space-between; }
.ed .soc-head output { font-family: var(--mono); font-size: 11px; color: var(--text); }
/* The fill is painted from --fill rather than left to accent-color, which fills
   to the middle of the thumb and so shows a stub of colour at the very bottom of
   the range, where nothing is filled at all. */
.ed .soc-box input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 14px; background: none; cursor: pointer; padding: 0; border: none;
}
.ed .soc-box input[type="range"]::-webkit-slider-runnable-track {
  height: 4px; border-radius: 2px;
  background: linear-gradient(to right, var(--accent) 0 var(--fill, 0%),
                                        var(--surface-3) var(--fill, 0%) 100%);
}
.ed .soc-box input[type="range"]::-moz-range-track {
  height: 4px; border-radius: 2px;
  background: linear-gradient(to right, var(--accent) 0 var(--fill, 0%),
                                        var(--surface-3) var(--fill, 0%) 100%);
}
.ed .soc-box input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; margin-top: -4px;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--surface);
}
.ed .soc-box input[type="range"]::-moz-range-thumb {
  width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--surface);
  background: var(--accent);
}
.ed .soc-box input[type="range"]:disabled { cursor: default; opacity: .5; }
/* ".ed .chk" asks for a row, but "#builder label" outranks it and stacks the box
   above its own text. The same rule, at a weight that wins. */
#builder .chk { flex-direction: row; align-items: center; gap: 6px; width: fit-content; }
/* Which of the two discharge-curve editors the solve reads. Both stay on the
   page; only the mark and the dimming move. */
#builder .curve-sec { display: flex; align-items: center; gap: 8px; }
#builder .curve-inuse { color: var(--accent); }
#builder .curve-unused { color: var(--text-dim); font-weight: 400; }
#builder .kind-fields { display: flex; flex-direction: column; gap: 10px; border-top: 1px dashed var(--border); padding-top: 10px; }
.curve { border: 1px solid var(--border); border-radius: 8px; padding: 8px; background: var(--panel); }
.curve-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.curve-head .cname { font-weight: 600; color: var(--text); }
.curve.collapsed { padding-bottom: 8px; }
.curve.collapsed .curve-head { margin-bottom: 0; }
.toggle { font-size: 11px; color: var(--accent); }
.curve-sum { font-size: 10px; color: var(--text-dim); margin-left: auto; margin-right: 4px; }
.pts { display: grid; grid-template-columns: 1fr 1fr auto; gap: 4px 8px; align-items: center; }
.pts .hd { font-size: 10px; color: var(--text-dim); }
.pts input { width: 100%; }
.mini { border: none; background: none; cursor: pointer; color: var(--text-dim); font-size: 14px; padding: 0 4px; }
.addbtn { align-self: flex-start; font-size: 11px; padding: 3px 8px; cursor: pointer; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-2); color: var(--text); }
.form-actions { display: flex; gap: 10px; margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
.form-actions .save { background: var(--accent); color: #fff; border: none; padding: 8px 18px; border-radius: 7px; font-weight: 600; cursor: pointer; }
.form-actions .del { margin-left: auto; background: none; border: 1px solid var(--danger); color: var(--danger); padding: 8px 14px; border-radius: 7px; cursor: pointer; }
.form-actions .cancel { background: none; border: 1px solid var(--border); padding: 8px 14px; border-radius: 7px; cursor: pointer; color: var(--text); }
.form-err { color: var(--danger); font-size: 12px; }
.paste { margin-top: 8px; display: flex; flex-direction: column; gap: 5px; }
.paste textarea { font-family: var(--mono); font-size: 11px; resize: vertical; }
.pins-tbl { display: grid; gap: 4px 8px; align-items: center; }
.pins-tbl .hd { font-size: 10px; color: var(--muted); }

/* ---- Project List Items ---- */
.proj-item {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  margin-bottom: 8px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.proj-item:hover {
  border-color: var(--accent);
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
/* The name takes the room, the badge takes what it needs, and everything an
   owner can DO to the project is behind the one button on the right. Six
   controls used to sit here inside a 600 px dialog: they wrapped, they pushed
   the name out of the way, and the one that deletes the project sat a few
   pixels from the one that opens it. */
.proj-item-header {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
}
.proj-item .proj-title {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
}
.proj-badge {
  flex: none; font-size: 11px; color: var(--text-dim);
  border: 1px solid var(--border); border-radius: 100px;
  padding: 2px 9px; background: var(--surface-2); white-space: nowrap;
}
.proj-badge.warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, var(--border)); }
.proj-badge.muted { color: var(--text-faint); }
.proj-more { flex: none; font-size: 16px; line-height: 1; }
.proj-item:hover .proj-more { color: var(--text); }

/* Renaming happens on the name, in its place. */
.proj-rename { flex: 1; display: flex; gap: 6px; min-width: 0; }
.proj-rename input { flex: 1; min-width: 0; }

#projCreateBtn { padding: 5px 10px; border-radius: 5px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); cursor: pointer; }
#projCreateBtn:hover { background: var(--surface-3); }

.proj-btn {
  flex: none; padding: 4px 10px; font-size: 12px;
  border-radius: 5px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); cursor: pointer;
  transition: background .15s;
}
.proj-btn:hover { background: var(--surface-3); }
.proj-btn.ghost { background: none; color: var(--text-dim); }
.proj-btn.ghost:hover { color: var(--text); background: var(--surface-3); }
/* One glyph, so it is a square rather than a word-shaped button. */
.proj-x {
  flex: none; width: 22px; height: 22px; padding: 0; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 5px; background: none;
  color: var(--text-faint); font-size: 15px; cursor: pointer;
}
.proj-x:hover { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }

.proj-item input[type="text"], .proj-item input[type="email"], .proj-item select {
  padding: 4px 8px;
  font-size: 12px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--surface-2);
  color: var(--text);
}

/* ---- what "Team..." and "Versions..." open ----
   A table each, under the project it belongs to. They are not in the menu
   because a popover is no place to type an email address into. */
.proj-panel {
  margin-top: 10px; padding: 10px 12px;
  border: 1px solid var(--border-soft); border-radius: 7px;
  background: var(--surface-2); cursor: default;
}
.proj-panel[hidden] { display: none; }
.proj-panel-hd {
  font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-dim); margin-bottom: 8px;
}
.proj-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; font-size: 12px; }
.proj-row .who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; color: var(--text); }
.proj-empty { color: var(--text-faint); font-size: 11.5px; padding: 2px 0; }
.proj-add { display: flex; gap: 6px; margin-top: 9px; padding-top: 9px;
  border-top: 1px solid var(--border-soft); }
.proj-add input { flex: 1; min-width: 0; }
.pins-tbl input { width: 100%; font-size: 11px; padding: 4px 6px; border: 1px solid var(--border); border-radius: 5px; background: var(--surface-2); color: var(--text); }
.pins-tbl .pin-group { font: 700 10px var(--font-mono); color: var(--accent); padding-top: 4px; margin-top: 2px; border-top: 1px dashed var(--border); }
.small-chk { flex-direction: row !important; align-items: center; gap: 6px; font-size: 11px; }

/* Utility & Component Classes */
.overlay-modal { background: rgba(0,0,0,0.8); z-index: 1000; }
.dialog-auth { margin-top: 20vh; }
.dialog-proj { margin-top: 10vh; max-width: 600px; height: auto; max-height: 80vh; }
.dialog-logout { width: 320px; height: auto; }
.form-group { display: flex; flex-direction: column; gap: 0.8rem; }
.form-row { display: flex; gap: 0.5rem; }
.input-fluid { width: 100%; box-sizing: border-box; }
.flex-1 { flex: 1; }
.flex-2 { flex: 2; }
.err-msg { color: var(--danger); font-size: 0.9em; margin-bottom: 0.5rem; }
.kpis-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-4); margin-bottom: var(--sp-5); }
.header-logo { height: 60px; margin-right: 16px; vertical-align: middle; cursor: pointer; }
.cursor-pointer { cursor: pointer; }
.margin-t-16 { margin-top: 16px; }
.margin-b-10 { margin-bottom: 10px; }
.w-100 { width: 100%; }
.user-info-text { font-size: 0.8rem; opacity: 0.8; margin-right: 1rem; margin-left: auto; cursor: pointer; }
.lock-banner { color: var(--warn); font-size: 0.8em; margin-left: 0.5rem; display: none; }
.modal-body-pad { padding: 1.5rem; display: block; }
.modal-body-scroll { padding: 1.5rem; display: flex; flex-direction: column; gap: 1rem; overflow-y: auto; max-height: calc(80vh - 50px); }
.proj-list-scroll { display: flex; flex-direction: column; max-height: 50vh; overflow-y: auto; }
.logout-text { text-align: center; padding: 1.5rem; display: block; }
.logout-p { margin-top: 0; margin-bottom: 1.5rem; }
.btn-pad { padding: 0 1.2rem; }
.proj-input { flex: 1; padding: 0.6rem; border: 1px solid var(--line); border-radius: 6px; font-size: 14px; }
.calcgrid { display: grid; }
.calc-label { flex: 0 0 120px; }
.calc-label-muted { flex: 0 0 120px; font-size: 11px; color: var(--muted); }
.digitize-link { text-transform: none; font-weight: normal; color: var(--accent); text-decoration: underline; }
.pins-tbl { display: grid; }
.font-11 { font-size: 11px; margin-left: 10px; }
.justify-center { justify-content: center; }
.flex-between { display: flex; justify-content: space-between; }

/* Premium Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.6rem 1.2rem;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  border: none;
  border-radius: var(--r-md);
  cursor: pointer;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.btn:active {
  transform: translateY(0);
}
.btn-primary {
  background: var(--accent);
  color: white;
  box-shadow: 0 2px 8px rgba(47, 141, 158, 0.2);
}
.btn-primary:hover {
  background: var(--accent-dim);
  box-shadow: 0 4px 12px rgba(47, 141, 158, 0.3);
}
.btn-danger {
  background: var(--danger);
  color: white;
  box-shadow: 0 2px 8px rgba(226, 97, 75, 0.2);
}
.btn-danger:hover {
  background: #f06a52;
  box-shadow: 0 4px 12px rgba(226, 97, 75, 0.3);
}
.btn-outline {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
}
.btn-outline:hover {
  background: var(--surface-2);
  border-color: var(--text-faint);
}
.dialog-logout .logout-text {
  padding: 2rem 1.5rem;
}
.dialog-logout .logout-p {
  font-size: 1.1rem;
  font-weight: 500;
  margin-bottom: 2rem;
  color: var(--text);
}


/* Premium Inputs */
.input-fluid {
  width: 100%;
  padding: 0.6rem 0.8rem;
  margin-top: 0.5rem;
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 13px;
  transition: all 0.2s ease;
}
.input-fluid:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(47, 141, 158, 0.15);
}
.input-fluid::placeholder {
  color: var(--text-faint);
}

/* Auth Modal layout improvements */
.dialog-auth .form-group label {
  color: var(--text-dim);
  font-weight: 500;
  font-size: 12px;
}
.margin-t-16 {
  margin-top: 1.5rem;
}

/* Global Settings Panel Premium Styling */
.global-settings-panel {
  margin-top: 24px;
  margin-bottom: 24px;
}
.global-settings-panel > h3 {
  font-size: 15px !important;
  letter-spacing: 1px;
  border-bottom: 2px solid var(--accent);
  display: inline-block;
  padding-bottom: 6px;
  margin-bottom: 20px !important;
  margin-top: 0;
  color: var(--accent);
}
.global-settings-category {
  background: var(--surface-2);
  border: 1px solid var(--border);
  box-shadow: var(--node-shadow);
  padding: 20px !important;
  border-radius: var(--r-md) !important;
  transition: all 0.3s ease;
}
.global-settings-category:hover {
  border-color: var(--border-hover);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
}
.global-settings-category h4 {
  font-size: 13px !important;
  color: var(--accent) !important;
  letter-spacing: 0.5px;
  border-bottom: 1px solid rgba(255,255,255,0.05) !important;
  padding-bottom: 8px !important;
  margin-bottom: 16px !important;
}
.global-settings-panel label {
  color: var(--text) !important;
  font-weight: 500;
  margin-bottom: 12px;
}
.global-settings-panel input, .global-settings-panel select {
  width: 100%;
  padding: 8px 12px !important;
  margin-top: 6px;
  background: var(--surface-3) !important;
  border: 1px solid var(--border) !important;
  border-radius: var(--r-sm) !important;
  color: var(--text) !important;
  font-family: var(--font-sans);
  font-size: 13px !important;
  transition: all 0.2s ease;
}
.global-settings-panel input:hover, .global-settings-panel select:hover {
  border-color: var(--accent-dim) !important;
  background: var(--surface-2) !important;
}
.global-settings-panel input:focus, .global-settings-panel select:focus {
  outline: none;
  border-color: var(--accent) !important;
  box-shadow: 0 0 0 3px rgba(47, 141, 158, 0.2);
}.dialog-auth .modal-body-pad {
  padding: 1.5rem 2rem 2rem 2rem;
}


.results-layout {
  display: flex;
  gap: 24px;
  align-items: flex-start;
}
.results-sidebar {
  flex: 0 0 340px;
}
.results-main {
  flex: 1;
  min-width: 0;
}
.excel-tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  background: var(--surface-2);
  border-radius: var(--r-sm);
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  border: 1px solid var(--border);
}
.excel-tbl th, .excel-tbl td {
  border: 1px solid var(--border);
  padding: 0;
  height: 28px;
}
.excel-tbl th {
  background: var(--surface-3);
  font-weight: 600;
  text-align: left;
  color: var(--text);
  padding: 4px 8px;
}
.excel-tbl td.lbl {
  background: var(--surface-2);
  color: var(--text);
  width: 50%;
  padding: 4px 8px;
}
.excel-tbl td.lbl.group {
  background: var(--surface-3);
  font-weight: 600;
  color: var(--accent);
  text-align: center;
}
.excel-tbl td.val {
  background: var(--surface);
}
.excel-tbl td.val input, .excel-tbl td.val select {
  width: 100%;
  height: 28px;
  box-sizing: border-box;
  border: none;
  background: transparent;
  padding: 0 8px;
  font-family: inherit;
  font-size: 13px;
  color: var(--text);
  font-weight: 500;
  outline: none;
  cursor: pointer;
}
.excel-tbl td.val input:focus, .excel-tbl td.val select:focus {
  background: var(--surface-3);
  box-shadow: inset 0 0 0 1.5px var(--accent);
}
.excel-tbl td.val input:hover, .excel-tbl td.val select:hover {
  background: rgba(255,255,255,0.03);
}

.excel-tbl td.val select option {
  background: var(--surface-2);
  color: var(--text);
}

/* ---- validation logs ---- */
#findings {
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 6px 12px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  max-height: calc(1.4em * 3 + 12px); /* approx 3 lines */
  overflow-y: auto;
  text-align: left;
  line-height: 1.4;
}
#findings .finding {
  display: inline;
}
#findings .f-error {
  color: var(--danger);
}
#findings .f-warn {
  color: var(--warn);
}
#findings .f-error b, #findings .f-warn b {
  display: inline;
  margin-right: 4px;
}

/* ---------- canvas overlay toggles (topbar I / P / eta / V) ---------- */
#svg.hide-i .ov-i { display: none; }
#svg.hide-p .ov-p { display: none; }
#svg.hide-eff .ov-eff { display: none; }
#svg.hide-v .wire-voltage { display: none; }

.tb-ov-group { display: flex; gap: 2px; align-items: center; }
.tb-ov-group .tb-btn.ov { padding: 2px 8px; font-size: 11px; opacity: .55; }
.tb-ov-group .tb-btn.ov.active { opacity: 1; }

/* peak mode indicator on the topbar battery text */
#battery.peak-on { color: var(--warnc, #d9a03f); font-weight: 600; }


/* ---- one figure, blown up to the window (js/figzoom.js) ----
   Every chart on the page is drawn at the width three of them fit in, which is
   the right size for the shape and too small for a tick label. */
.fig-zoom {
  flex: none; background: none; border: 1px solid transparent; border-radius: 4px;
  color: var(--text-faint); cursor: pointer; line-height: 0; padding: 3px;
}
.fig-zoom:hover { color: var(--text); background: var(--surface-3); border-color: var(--border); }
.fig-zoom:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The library's plots are a bare div, so the button rides in the corner. */
.fig-zoomable { position: relative; }
.fig-zoomable > .fig-zoom { position: absolute; top: 2px; right: 2px; }

/* Above the library modal and the calculation sheet: it is opened from inside
   both, and a picture behind the window it came from is no use. */
.fz {
  position: fixed; inset: 0; z-index: 9998; flex-direction: column;
  background: var(--bg); color: var(--text);
}
.fz:focus { outline: none; }
.fz-head {
  display: flex; align-items: center; gap: 4px; flex: none;
  padding: 6px 8px 6px 14px; background: var(--surface-2);
  border-bottom: 1px solid var(--border); font-size: 12px; user-select: none;
}
.fz-title { color: var(--text-dim); font-size: 11px; letter-spacing: .06em;
  text-transform: uppercase; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.fz-sp { flex: 1; }
.fz-zv { min-width: 52px; font-variant-numeric: tabular-nums; }
.fz-body {
  flex: 1; min-height: 0; overflow: auto; padding: 12px;
  display: flex; overscroll-behavior: contain;
  /* "safe", or a picture zoomed past the window is centred into its own
     overflow: the left edge and the whole y axis end up at a negative offset
     that nothing can scroll back to. */
  align-items: safe center; justify-content: safe center;
}
/* flex: none, or the stage is squeezed back to the window when it is zoomed
   past it, and the body never gets anything to scroll. */
.fz-stage { flex: none; }
/* The stage wears the figure's classes so its marks keep their styling; the
   card those classes also draw is not wanted a second time round the picture. */
.fz-stage.bat-plot { flex: none; border: none; background: none; padding: 0;
  --bp-paper: var(--bg); }
.fz-stage svg { display: block; }
.fz-foot { flex: none; margin: 0; padding: 8px 14px 12px;
  border-top: 1px solid var(--border); background: var(--surface-2);
  max-height: 22vh; overflow: auto; }
body.fz-open { overflow: hidden; }


/* ---- "a solve is running" (js/battery.js, js/results.js) ----
   Every number on either page is one server round trip, and on a real tree that
   is about a second. The dimming and the chip are both held back a quarter of a
   second: a solve that comes home in eighty milliseconds must not flash a
   loading state at anybody. Only the busy rules carry the delay, so coming
   back is immediate.

   One idiom, one set of class names, both pages - the "bat" in them is where
   they were written, not where they belong. */
#batteryPage, #results { position: relative; }

/* Sticky and zero-height: pinned to the top of the page whatever the scroll,
   and it takes no room in the layout it is floating over. */
.bat-veil { position: sticky; top: 0; z-index: 5; height: 0;
  display: flex; justify-content: center; pointer-events: none; }
.bat-chip {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 8px;
  padding: 5px 13px 5px 11px; border-radius: 999px; white-space: nowrap;
  background: var(--surface-2); border: 1px solid var(--border);
  box-shadow: var(--shadow); color: var(--text-dim); font-size: 11.5px;
  opacity: 0; transform: translateY(-9px) scale(.96);
  transition: opacity .18s ease, transform .18s ease;
}
.bat-chip i { width: 11px; height: 11px; border-radius: 50%; flex: none;
  border: 2px solid var(--accent-dim); border-top-color: var(--accent); }
.bat-busy .bat-chip {
  opacity: 1; transform: none; transition-delay: .25s;
}
/* Spinning only while it is up: an animation on a hidden element is a repaint
   nobody sees, on every frame, forever. */
.bat-busy .bat-chip i { animation: bat-spin .7s linear infinite; }
@keyframes bat-spin { to { transform: rotate(360deg); } }

/* The numbers still on screen belong to the previous solve. Fading them says
   so, where leaving them lit says the page is live when it is not. */
.bat-body { transition: opacity .18s ease; }
.bat-busy .bat-body { opacity: .42; transition-delay: .25s; }
/* ...except on the first visit, where the body IS the waiting state. */
.bat-busy.bat-first .bat-body { opacity: 1; }

/* The page's own shape, in grey, until the first solve lands.
   surface-3 against border, because those two are a step apart in BOTH themes:
   the lighter pair reads on a dark background and disappears on a white card. */
.bat-skel .sk { border-radius: 5px; background: var(--surface-3);
  background-image: linear-gradient(90deg,
    transparent 0%, var(--border) 45%, transparent 90%);
  background-size: 220% 100%; background-repeat: no-repeat;
  animation: sk-sweep 1.5s ease-in-out infinite; }
@keyframes sk-sweep { from { background-position: 120% 0; }
                        to { background-position: -120% 0; } }
.bat-skel .kpi { pointer-events: none; }
.bat-skel .sk-stack { display: flex; flex-direction: column; gap: 7px; }
.bat-skel .sk-strip { display: flex; align-items: center; gap: 10px; }
.bat-skel .sk-table { display: flex; flex-direction: column; gap: 11px;
  border: 1px solid var(--border-soft); border-radius: var(--r-sm); padding: 12px; }
.bat-skel .sk-row { display: flex; align-items: center; gap: 18px; }
.bat-skel .sk-row .sk { flex: none; }
.bat-skel .sk-figs { display: flex; flex-wrap: wrap; gap: 16px; }

/* Nothing here should move for a reader who has asked for less of that. */
@media (prefers-reduced-motion: reduce) {
  .bat-busy .bat-chip i, .bat-skel .sk { animation: none; }
  .bat-chip, .bat-body { transition: none; }
}
