:root {
  --bg: #f4f6f8;
  --panel: #ffffff;
  --ink: #16202a;
  --muted: #6b7885;
  --line: #dfe4e9;
  --accent: #1f6feb;
  --ok: #157347;
  --bad: #b02a37;
  --shadow: 0 1px 2px rgba(22, 32, 42, .08);
}
* { box-sizing: border-box; }
body {
  margin: 0;
  font: 14px/1.5 Arial, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--ink);
}
.mono { font-family: Consolas, "Courier New", monospace; }
.muted { color: var(--muted); }

.topbar {
  display: flex; align-items: center; gap: 20px;
  background: #16202a; color: #fff; padding: 0 20px; height: 52px;
}
.topbar .brand { color: #fff; font-weight: bold; text-decoration: none; letter-spacing: .3px; }
.topbar nav a { color: #c9d4de; text-decoration: none; margin-right: 16px; }
.topbar nav a:hover { color: #fff; }
.topbar .right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.topbar .who { color: #c9d4de; }
.topbar .who small { background: #2c3a48; padding: 1px 6px; border-radius: 3px; margin-left: 6px; }
.topbar button.link { background: none; border: 1px solid #3a4855; color: #c9d4de; padding: 4px 10px; border-radius: 4px; cursor: pointer; }
.topbar button.link:hover { color: #fff; border-color: #6b7885; }

main { max-width: 900px; margin: 28px auto; padding: 0 20px; }
main.wide { max-width: 1180px; }
main.narrow { max-width: 620px; }
h1 { font-size: 22px; margin: 0 0 4px; }
h2 { font-size: 15px; margin: 0 0 14px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); }

.page-head { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; }

.card {
  background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
  padding: 18px; margin-bottom: 16px; box-shadow: var(--shadow);
}
.card.placeholder { text-align: center; padding: 40px 20px; }

label { display: block; margin-bottom: 12px; font-weight: bold; font-size: 13px; }
label.check { font-weight: normal; display: flex; align-items: center; gap: 6px; }
input, select {
  display: block; width: 100%; margin-top: 4px; padding: 7px 9px;
  border: 1px solid var(--line); border-radius: 4px; font: inherit; background: #fff;
}
input:focus, select:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
input.readonly { background: #eef1f4; color: var(--muted); }

.field-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 0 18px; }
.field-grid label.wide { grid-column: 1 / -1; }

button.primary, .primary.button {
  display: inline-block; background: var(--accent); color: #fff; border: 0;
  padding: 9px 18px; border-radius: 4px; font: inherit; font-weight: bold;
  cursor: pointer; text-decoration: none;
}
button.primary:hover { background: #1a5fcc; }
button.primary:disabled { background: #9db6dd; cursor: not-allowed; }
.actions { margin-bottom: 24px; }

.hint { display: block; font-weight: normal; color: var(--muted); margin-top: 4px; }
.hint.ok { color: var(--ok); }
.hint.bad { color: var(--bad); }

.alert {
  background: #fdecee; border: 1px solid #f5c2c7; color: var(--bad);
  padding: 10px 12px; border-radius: 4px; margin: 0 0 14px;
}
.ok-banner {
  background: #e7f4ec; border: 1px solid #b7dfc6; color: var(--ok);
  padding: 8px 12px; border-radius: 4px;
}
.empty { color: var(--muted); padding: 30px; text-align: center; }

.filters { display: flex; gap: 10px; align-items: center; margin-bottom: 14px; }
.filters input[type=search] { max-width: 320px; }
.filters button { padding: 7px 14px; border: 1px solid var(--line); background: #fff; border-radius: 4px; cursor: pointer; font: inherit; }

table.grid { width: 100%; border-collapse: collapse; background: var(--panel); border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
table.grid th, table.grid td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--line); }
table.grid thead th { background: #eef1f4; font-size: 12px; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); }
table.grid tbody tr:last-child td { border-bottom: 0; }
table.grid a { color: var(--accent); }

.tabs { display: flex; flex-wrap: wrap; gap: 2px; border-bottom: 2px solid var(--line); margin-bottom: 16px; }
.tab {
  padding: 8px 14px; text-decoration: none; color: var(--muted);
  background: #e8ecf0; border: 1px solid var(--line); border-bottom: 0;
  border-radius: 4px 4px 0 0; font-size: 13px;
}
.tab:hover { color: var(--ink); }
.tab.active { background: var(--panel); color: var(--ink); font-weight: bold; }
.tab.pending { opacity: .55; font-style: italic; }

.results { margin-top: 16px; }
.results table.grid th { width: 220px; background: #f7f9fa; }
.revlog { list-style: none; padding: 0; margin: 0; }
.revlog li { padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.revlog li:last-child { border-bottom: 0; }

main.login { max-width: 380px; margin-top: 90px; }
main.login h1 { font-size: 20px; }
main.login .card { padding: 26px; }
main.login button { width: 100%; margin-top: 6px; }

/* ---------------------------------------------------------------
   Phase 2 — equipment catalogs
   --------------------------------------------------------------- */

.catalog-grid{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
  gap:16px;
  margin-bottom:24px;
}
.catalog-card{
  display:block;
  padding:20px;
  background:#fff;
  border:1px solid #d7dde3;
  border-radius:6px;
  text-decoration:none;
  color:inherit;
  transition:border-color .12s, box-shadow .12s;
}
.catalog-card:hover{border-color:#2f6f9f;box-shadow:0 1px 4px rgba(20,40,60,.10);}
.catalog-card h2{margin:0 0 8px;font-size:1.1rem;}
.catalog-card .count{margin:0 0 6px;font-size:1.7rem;font-weight:600;line-height:1;}
.catalog-card .count span{font-size:.8rem;font-weight:400;color:#6a7683;}

.filters{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:16px;}
.filters input[type=search]{
  flex:1 1 260px;min-width:180px;padding:8px 10px;
  border:1px solid #c3ccd4;border-radius:4px;font-size:.95rem;
}
.filters .inline{display:flex;align-items:center;gap:6px;font-size:.9rem;color:#4a5563;white-space:nowrap;}
.filters .inline input{margin:0;}

.filter-inline{
  width:100%;max-width:340px;margin:12px 0;padding:8px 10px;
  border:1px solid #c3ccd4;border-radius:4px;font-size:.95rem;
}

.table-wrap{overflow-x:auto;border:1px solid #d7dde3;border-radius:6px;background:#fff;}
table.data{border-collapse:collapse;width:100%;font-size:.9rem;}
table.data th,table.data td{padding:8px 12px;text-align:left;border-bottom:1px solid #e6eaee;white-space:nowrap;}
table.data th{
  background:#f4f7f9;font-weight:600;font-size:.78rem;
  text-transform:uppercase;letter-spacing:.04em;color:#5a6673;
  position:sticky;top:0;z-index:1;
}
table.data th a{color:inherit;text-decoration:none;}
table.data th a:hover{color:#2f6f9f;}
table.data th small{font-weight:400;text-transform:none;letter-spacing:0;}
table.data th .caret{color:#2f6f9f;font-size:.7em;}
table.data td.num,table.data th.num{text-align:right;font-variant-numeric:tabular-nums;}
table.data tbody tr:hover{background:#f7fafc;}
table.data tr.inactive{opacity:.5;}
table.data tr:last-child td{border-bottom:none;}

.kv{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:2px 18px;}
.kv div{display:flex;justify-content:space-between;gap:12px;padding:5px 0;border-bottom:1px solid #eef1f4;}
.kv dt{margin:0;color:#6a7683;font-size:.85rem;font-family:ui-monospace,Menlo,Consolas,monospace;}
.kv dd{margin:0;font-size:.9rem;text-align:right;}

.chips{display:flex;gap:8px;flex-wrap:wrap;margin:0;}
.chip{
  display:inline-block;padding:3px 9px;background:#eaf2f8;border:1px solid #c5dbeb;
  border-radius:12px;font-size:.8rem;font-family:ui-monospace,Menlo,Consolas,monospace;color:#245f88;
}

.crumb{margin:0 0 4px;font-size:.85rem;color:#6a7683;}
.crumb a{color:#2f6f9f;text-decoration:none;}
.crumb a:hover{text-decoration:underline;}

.split{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap;}
.actions{display:flex;gap:10px;margin-top:16px;}
.small{font-size:.85rem;}
.unit{color:#8a95a1;font-weight:400;}
.quiet-link{color:#6a7683;text-decoration:none;font-size:.85rem;}
.quiet-link:hover{color:#2f6f9f;text-decoration:underline;}
.btn.quiet{background:#fff;color:#4a5563;border:1px solid #c3ccd4;}
.err-banner{
  background:#fdecea;border:1px solid #f5c2bd;color:#8d2b20;
  padding:10px 14px;border-radius:4px;margin-bottom:16px;
}
.card.note{background:#f7fafc;}
label.wide{grid-column:1 / -1;}

/* --- Helioscope paste import --- */
.paste-box{
  width:100%;
  font-family:ui-monospace,Menlo,Consolas,monospace;
  font-size:.85rem;
  line-height:1.5;
  padding:10px 12px;
  border:1px solid #c3ccd4;
  border-radius:4px;
  resize:vertical;
  white-space:pre;
  overflow-x:auto;
}
.paste-box:focus{outline:2px solid #2f6f9f;outline-offset:-1px;border-color:#2f6f9f;}
.cell-in{
  width:92px;padding:4px 6px;font-size:.85rem;
  border:1px solid #d0d8df;border-radius:3px;
}
.cell-in.narrow{width:64px;}
.cell-in:focus{border-color:#2f6f9f;outline:none;}
button.quiet-link{background:none;border:none;cursor:pointer;padding:0;font:inherit;}

/* ---------------------------------------------------------------------------
   Phase 3 — DC Side and string sizing
   --------------------------------------------------------------------------- */

.group-head {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted, #6b7280);
  margin: 1.25rem 0 0.5rem;
  font-weight: 600;
}
.group-head:first-of-type { margin-top: 0.25rem; }

table.data th.rowlab {
  text-align: left;
  white-space: nowrap;
  font-weight: 600;
}
table.data tr.subhead th {
  font-weight: 500;
  font-size: 0.75rem;
  border-top: 0;
  padding-top: 0;
}
table.data tr.muted td,
table.data tr.muted th { opacity: 0.45; }

/* The headline number on the string sizing tab — an engineer should be able to
   read it from across the room without hunting through the table. */
.big-result {
  font-size: 1.9rem;
  font-weight: 600;
  line-height: 1.1;
  text-align: right;
  margin: 0;
  white-space: nowrap;
}
.big-result .small {
  display: block;
  font-size: 0.72rem;
  font-weight: 400;
}

/* Warnings that qualify a result, as distinct from ones that block it. */
.alert.warn {
  background: #fff8e1;
  border-color: #f0c36d;
  color: #6b4e00;
}

/* An impossible string window is a real workbook result, not an error — the VBA
   computes min and max independently and never cross-checks them. Show it as a
   verdict rather than a backwards range. */
.big-result.invalid {
  font-size: 1.25rem;
  color: #9a3412;
}

/* ---------------------------------------------------------------------------
   Phase 4 — POI-1
   --------------------------------------------------------------------------- */

.summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 0.75rem 1.25rem;
  margin-top: 0.5rem;
}
.summary-grid .k {
  display: block;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted, #6b7280);
}
.summary-grid .v { display: block; font-size: 1.35rem; font-weight: 600; }
.summary-grid .v.bad { color: #b91c1c; }

/* 25 rows x 4 string blocks is far too much to show at once, so each row
   collapses to a one-line summary and opens only when it has content. */
.poi-row {
  border: 1px solid var(--line, #e5e7eb);
  border-radius: 6px;
  margin-bottom: 0.5rem;
  background: #fff;
}
.poi-row > summary {
  cursor: pointer;
  padding: 0.55rem 0.75rem;
  display: flex;
  gap: 1rem;
  align-items: baseline;
  flex-wrap: wrap;
  font-size: 0.85rem;
}
.poi-row[open] > summary {
  border-bottom: 1px solid var(--line, #e5e7eb);
  background: #f9fafb;
}
.poi-label { font-weight: 600; min-width: 4.5rem; }

.str-block {
  border-top: 1px dashed var(--line, #e5e7eb);
  padding: 0.6rem 0.75rem;
}
.str-head {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  margin-bottom: 0.35rem;
}
.str-name {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted, #6b7280);
}

.field-grid.tight { gap: 0.4rem 0.75rem; padding: 0 0.75rem 0.5rem; }
.mp-grid { grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); }

/* The checkmark and the error tokens are the workbook's own vocabulary —
   designers read them at a glance, so they need to stay visually distinct. */
.chk-ok { color: #15803d; }
.chk-bad { color: #b91c1c; font-weight: 600; }

/* The sheet's tick is weaker than it looks for string inverters — SEARCH("OK")
   matches the "OK" inside "MP OK", so it passes whenever the mounting planes
   add up. The verdict beside it carries the real string-length answer. */
.chk-warn { color: #b45309; font-weight: 600; }
.comment-line { padding: 0 0.75rem 0.4rem; word-break: break-word; }

/* ---------------------------------------------------------------------------
   Searchable combobox (public/combobox.js)

   The native <select> stays in the DOM and keeps its name, so the form posts
   exactly as before and still works if the script fails to load. It is hidden
   visually rather than removed.
   --------------------------------------------------------------------------- */

.cb { position: relative; }
.cb .cb-native {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}
.cb-button {
  width: 100%;
  text-align: left;
  padding: 0.4rem 1.6rem 0.4rem 0.55rem;
  border: 1px solid var(--line, #cbd5e1);
  border-radius: 4px;
  background: #fff;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cb-button::after {
  content: '';
  position: absolute;
  right: 0.6rem;
  top: 50%;
  margin-top: -2px;
  border: 4px solid transparent;
  border-top-color: #64748b;
  pointer-events: none;
}
.cb-button.is-placeholder { color: #94a3b8; }
.cb-button:focus,
.cb.is-open .cb-button { outline: 2px solid #2563eb; outline-offset: -1px; }

.cb-panel {
  display: none;
  position: absolute;
  z-index: 40;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  min-width: 15rem;
  background: #fff;
  border: 1px solid var(--line, #cbd5e1);
  border-radius: 4px;
  box-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);
}
.cb.is-open .cb-panel { display: block; }
.cb.drop-up .cb-panel { top: auto; bottom: calc(100% + 2px); }

.cb-search {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--line, #e2e8f0);
  padding: 0.45rem 0.55rem;
  font: inherit;
  font-size: 0.85rem;
  border-radius: 4px 4px 0 0;
}
.cb-search:focus { outline: none; }

.cb-list { max-height: 15rem; overflow-y: auto; }
.cb-option {
  padding: 0.35rem 0.55rem;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cb-option.is-active { background: #eff6ff; }
.cb-option.is-selected { font-weight: 600; }
.cb-empty {
  display: block;
  padding: 0.5rem 0.55rem;
  font-size: 0.82rem;
  color: var(--muted, #64748b);
}

/* Free-text fields that offer suggestions keep a normal input plus a datalist,
   so a value not in the list is still allowed — matching the workbook, where
   these cells are typed rather than picked. */
input[list] { background-image: none; }

/* Free-text combobox (Label, LC Assign, MP names).

   Same panel as the select version so the two read as one control type, but
   the trigger stays a real text input — these fields accept values outside the
   list, which is how the workbook treats them. */
.cb-free .cb-input {
  width: 100%;
  padding: 0.4rem 1.6rem 0.4rem 0.55rem;
  border: 1px solid var(--line, #cbd5e1);
  border-radius: 4px;
  background: #fff;
  font: inherit;
  font-size: 0.85rem;
}
/* A real button rather than a pseudo-element — "click the arrow" should not
   depend on pointer-events pass-through. */
.cb-arrow {
  position: absolute;
  right: 1px;
  top: 1px;
  bottom: 1px;
  width: 1.5rem;
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 0;
}
.cb-arrow::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  margin: -2px 0 0 -4px;
  border: 4px solid transparent;
  border-top-color: #64748b;
}
.cb-free.is-open .cb-arrow::after {
  border-top-color: transparent;
  border-bottom-color: #64748b;
  margin-top: -6px;
}
.cb-free .cb-input:focus { outline: 2px solid #2563eb; outline-offset: -1px; }
.cb-free .cb-panel { min-width: 100%; }
.cb-free .cb-empty { display: block; font-style: italic; }

/* Computed columns — Group, DC Label, Group Ref Label. The workbook fills
   these by formula, so they are shown but not editable. Styled flat so they
   don't read as inputs the user has failed to fill in. */
input.computed {
  background: #f1f5f9;
  border-color: #e2e8f0;
  color: #475569;
  cursor: default;
}

/* Users admin screen */
.users-table td { vertical-align: middle; }
.users-table form.inline { display: inline-flex; gap: 0.3rem; margin: 0; }
.users-table form.inline input { width: 9rem; font-size: 0.8rem; padding: 0.25rem 0.4rem; }
.users-table form.inline select { font-size: 0.8rem; padding: 0.2rem 0.3rem; }
.users-table form.inline button { font-size: 0.8rem; padding: 0.25rem 0.55rem; }
.alert.ok { background: #ecfdf5; border-color: #6ee7b7; color: #065f46; }
pre.mono { background: #f8fafc; padding: 0.75rem; border-radius: 4px; overflow-x: auto; }

/* ---------------------------------------------------------------------------
   POI-1 issues panel

   The sheet counts errors into one cell and leaves you to find them. With rows
   collapsed by default that is worse here than in Excel, so the count links to
   the specific block.
   --------------------------------------------------------------------------- */

.card.issues h2 { font-size: 0.95rem; margin: 0 0 0.35rem; }
.card.issues h2.bad { color: #b91c1c; }
.card.issues h2.warn { color: #b45309; margin-top: 1.1rem; }

.issue-list { list-style: none; margin: 0.35rem 0 0; padding: 0; }
.issue-list li {
  padding: 0.4rem 0;
  border-top: 1px solid var(--line, #f1f5f9);
  font-size: 0.85rem;
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  align-items: baseline;
}
.issue-list li:first-child { border-top: 0; }
.issue-link { font-weight: 600; white-space: nowrap; }
.issue-token { white-space: nowrap; }

/* A marker in the collapsed header, so a problem is visible without opening
   all 25 rows. */
.row-flag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  font-size: 0.75rem;
  font-weight: 700;
  color: #fff;
  flex: none;
}
.row-flag.bad { background: #dc2626; }
.row-flag.warn { background: #d97706; }

.poi-row.has-issue { border-color: #fca5a5; }
.poi-row.has-issue > summary { background: #fef2f2; }
.poi-row.has-review > summary { background: #fffbeb; }

.str-block.blk-issue { background: #fef2f2; }
.str-block.blk-review { background: #fffbeb; }

/* Flash the row you jumped to — with 25 on screen, scrolling alone doesn't
   tell you which one moved. */
@keyframes just-jumped-flash {
  from { box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.55); }
  to   { box-shadow: 0 0 0 3px rgba(37, 99, 235, 0); }
}
.poi-row.just-jumped { animation: just-jumped-flash 1.4s ease-out; }

/* --- General Tools, Utility Equipment and the sheet-level AC rows ---------- */

/* A caution that is not an error: the utility fell back to generic equipment,
   or the service is off the top of the table. Distinct from .err-banner, which
   means the page could not do the thing at all. */
.warn-banner {
  margin: 0 0 .75rem;
  padding: .55rem .75rem;
  border-left: 3px solid #b58900;
  background: #fdf6e3;
  color: #6b5200;
  font-size: .9rem;
  border-radius: 0 3px 3px 0;
}

/* Section heading inside a data table — "Phase wires" / "Ground wires". */
.grid .row-group {
  text-align: left;
  font-weight: 600;
  font-size: .8rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #6b7280;
  background: #f7f8fa;
  padding-top: .5rem;
}

/* The catalog row this project actually resolved to. */
.grid tr.row-selected > td {
  background: #eef6ff;
  box-shadow: inset 2px 0 0 #2563eb;
}

/* One load centre / disconnect on the POI tab. Same rhythm as .str-block so
   the sheet-level rows read as part of the same form, not a separate screen. */
.ac-sheet-row {
  border-top: 1px solid #e5e7eb;
  padding: .85rem 0 .25rem;
}
.ac-sheet-row:first-of-type { border-top: 0; }

/* The AWG cell is a combobox, and the widget fills whatever it is given. In a
   five-column grid that pushes the numbers off to the right, so the conductor
   columns get explicit widths and the numeric ones take the slack. */
.grid.compact td .combobox,
.grid.compact td input.cell-in { width: 100%; }
.grid.compact col.col-awg   { width: 8.5rem; }
.grid.compact col.col-ins   { width: 7.5rem; }
.grid.compact col.col-qty   { width: 5rem; }


/* ---------------------------------------------------------------------------
   Presets — the admin screen for the workbook's house rules.

   Wide label column, because the name, its sheet cell and a sentence of
   explanation all belong together: an admin reading "preset_max_parallel_ix"
   with no context is being asked to change something blind.
   --------------------------------------------------------------------------- */
.presets-table td { vertical-align: top; padding-top: 0.55rem; padding-bottom: 0.55rem; }
.presets-table .preset-label { display: block; font-weight: 600; cursor: pointer; }
.presets-table label.inline { display: inline-flex; align-items: center; gap: 0.4rem; }
.presets-table label.inline input { margin: 0; }
.presets-table .cell-in { width: 100%; max-width: 9rem; }
/* A row that differs from the workbook: marked, not alarming. */
.presets-table tr.row-selected td:first-child { border-left: 3px solid #f59e0b; padding-left: 0.5rem; }
.presets-table form { margin: 0; }

/* HT:HX — the conduit allocation, one box per environment. It reads as a
   footnote to the run above it rather than as another set of sizing inputs,
   so it is tinted and its head sits on one line with the KC:KF schedule. */
.alloc-block { background: #f8fafc; }
.alloc-block .str-head { flex-wrap: wrap; }
.alloc-block .str-head .mono { white-space: nowrap; }

/* A sub-heading inside a card — the CT table and the BOM lines on the PV BESS
   block. Smaller than the card's own h2 and spaced off what precedes it. */
h3.sub {
  margin: 1.25rem 0 0.4rem;
  font-size: 0.95rem;
  font-weight: 600;
}

/* The AC wire schedule is twenty columns wide and will not fit a laptop at
   any sane font size, so it scrolls sideways inside its card rather than
   forcing the whole page to. */
.scroll-x { overflow-x: auto; }
.grid.small { font-size: 0.78rem; }
.grid.small th, .grid.small td { padding: 0.25rem 0.45rem; white-space: nowrap; }

/* The DC conductor run, above the AC one on each inverter row. Tinted the
   other way from the allocation block so the two halves of the row read as
   two halves rather than as one long list of fields. */
.dc-block { background: #f5f8fa; }

/* The DC string schedule is four repeated six-column blocks. A left rule on
   the first column of each block is the only thing that stops it reading as
   twenty-four undifferentiated columns. */
.grid.small th.grp, .grid.small td.grp { border-left: 2px solid var(--line, #e5e7eb); }
.grid.small thead tr:first-child th.grp { text-align: center; background: #f3f4f6; }

/* --- Buttons that are links, and the clone/settings forms ---------------- */
/* `.btn` did not exist: every button in the app was a <button class="primary">
   inside its own form. The settings and clone pages need a secondary action
   (Cancel) and the CustomProps card needs three download links, so both shapes
   are defined here and `.btn.primary` is made to match button.primary exactly,
   otherwise a link and a button sitting side by side in the same .actions row
   render at different heights. */
.btn {
  display: inline-block; background: #fff; color: #33414f;
  border: 1px solid #c3ccd4; padding: 8px 16px; border-radius: 4px;
  font: inherit; cursor: pointer; text-decoration: none; white-space: nowrap;
}
.btn:hover { background: #f2f5f8; border-color: #a9b5c0; color: var(--ink); }
.btn.primary {
  background: var(--accent); color: #fff; border-color: var(--accent); font-weight: bold;
}
.btn.primary:hover { background: #1a5fcc; border-color: #1a5fcc; color: #fff; }

.page-head .actions { margin: 0; }

/* The revision-history choice on the clone page. Radios read badly in the
   field-grid, which is built for labelled inputs, so they get their own
   block layout with the explanation beside the control rather than under it. */
fieldset.inset {
  border: 1px solid var(--line); border-radius: 4px;
  padding: 12px 16px 4px; margin: 18px 0 4px; box-shadow: none;
}
fieldset.inset legend { font-weight: bold; padding: 0 6px; }
label.radio {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0 0 12px; font-weight: normal; cursor: pointer;
}
label.radio input { width: auto; margin: 3px 0 0; flex: none; }
label.radio span { font-size: .9rem; color: #4a5563; }
label.radio strong { color: var(--ink); }

/* --- Expandable revision log ------------------------------------------- */
/* The log used to be one flat line per save. Each entry is now a <details>
   holding the field-level diff, so the collapsed view stays as scannable as
   it was and the detail is one click away. Entries written before the diff
   column existed render as .rev-flat: same layout, no disclosure triangle,
   because there is genuinely nothing to expand. */
.revlog li { padding: 0; }
.rev { font-size: 13px; }
.rev summary,
.rev-flat {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 7px 0; cursor: pointer; list-style: none;
}
.rev-flat { cursor: default; padding-left: 20px; }
.rev summary::-webkit-details-marker { display: none; }
.rev summary::before {
  content: "\25B8"; color: var(--muted); font-size: 11px;
  width: 10px; flex: none; transition: transform .12s ease;
}
.rev[open] summary::before { transform: rotate(90deg); }
.rev summary:hover { background: #f7fafc; }
.rev summary > .mono { flex: none; }
.rev-who { color: #4a5563; font-weight: bold; }
.rev-note { flex: 1 1 auto; min-width: 140px; }
/* Pushed to the right and quiet: it is a hint about whether expanding is
   worth it, not a number anyone reads on its own. */
.rev-count {
  flex: none; color: var(--muted); font-size: 11px;
  border: 1px solid var(--line); border-radius: 10px; padding: 1px 8px;
}
.rev-table { margin: 2px 0 12px 20px; width: calc(100% - 20px); }
.rev-table th { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.rev-table td { vertical-align: top; }
/* Colour carries the direction so the eye finds the new value without
   reading the column header each time. Kept desaturated: a revision log is
   not an alert. */
.rev-from { color: #8d5a54; text-decoration: line-through; text-decoration-color: #d9b6b1; }
.rev-to { color: #1d6b45; }
