/* Duty Call — the whole stylesheet. One file while it stays this size.
 *
 * Contrast is measured, not eyeballed. Against the panel (#1b1e22):
 *   --ink  #e9ecef  13.9:1   body text
 *   --dim  #a9b2bc   7.4:1   hints and secondary text (clears the 7:1 bar)
 *   --line #333941   1.9:1   borders only, never text
 * The canvas ground is a neutral mid-grey so both white and black geometry
 * show their edges against it, with a hairline border.
 */

:root{
  --bg:#111316; --bar:#1d2024; --panel:#1b1e22; --line:#333941;
  --ink:#e9ecef; --dim:#a9b2bc;

  /* ---- the gold ----
     Black ground, white type, one saturated gold for every call to action.
     --gold-deep is for borders and pressed states, --gold-lift for the top of
     the logo's fade. Text ON gold is always --bg, never white: gold is a light
     colour, so white on it measures about 1.4:1 and is unreadable.

     --brand and --mark are the same gold today but stay SEPARATE TOKENS on
     purpose. --brand means "interactive" (focus rings, splitters, drop
     targets, progress); --mark means "identity" (the logo and its hover).
     They are independent concerns that happen to agree right now — do not
     collapse them into one, or changing the logo silently restyles every
     focus ring in the app. */
  --gold:#ffc400; --gold-deep:#e0a600; --gold-lift:#ffd84d;
  --brand:var(--gold);
  --mark:var(--gold);
  --ground:#6e7378;                 /* mid-grey behind the plan view */
  /* Measurement colours, NOT theme. These say over/under and must stay
     red/blue however the brand moves — recolouring them to match the accent
     would make the meter's verdict unreadable at a glance. */
  --red:#ff5c5c; --blue:#5cc0ff;

  /* ---- type ----
     Two families and six sizes. Before this the app had ten sizes between 10
     and 14px -- 10, 11, 11.5, 12, 12.5, 13, 13.3, 14 -- which is not a scale,
     it is drift: neighbours half a pixel apart read as inconsistency rather
     than hierarchy, and 13.3 was not chosen at all (see the shorthand trap
     below). Reach for an existing step rather than inventing one between two.

     NEVER write `font: <weight> <size>/<lh> inherit`. `inherit` is not a legal
     family in the font shorthand, so the WHOLE declaration is dropped and the
     element silently falls back to the UA default -- that bug was live on
     .btn, .vtab and .slot-title, which is why buttons were rendering in Arial
     and the slot titles were never bold. Always name a family token. */
  --f-sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  --f-mono:ui-monospace,"Cascadia Mono","Segoe UI Mono",Consolas,monospace;
  /* THE ROLES, not just the sizes. Every piece of text in the app is one of
     these, and a new string takes the nearest role rather than a new size:

       LABEL   10px  mono 600 caps +.14em  dim   pane captions, log tabs,
                                                 runs-locally, the 2D chip
       FINE    11.5px sans 400             dim   hints, notes, sub-labels
       HEADING 11.5px mono 600 caps +.14em dim   the panel's numbered steps
       BODY    13px  sans 400 / 600        ink   panel content; 600 wherever
                                                 it OPERATES (buttons, tabs)
       BASE    14px  sans 400              ink   prose read in full
       LEAD    15px  sans 700              #fff  slot titles, emphasis
       CTA     14px  sans 700 caps +.06em  bg    the ONE primary action
       DISPLAY 46px  sans 800 -.03em       ink   the result, nothing else

     Weight says WHAT KIND: 400 reads, 600 operates, 700 leads, 800
     announces. Colour says HOW MUCH: --dim quiet, --ink content, #fff only
     where something must outrank ink, --bg only on gold. Do not invent a
     new combination without adding it to this table. */
  --t-label:10px;
  --t-fine:11.5px;
  --t-body:13px;
  --t-base:14px;
  --t-lead:15px;
  --t-huge:46px;
}
*{ box-sizing:border-box; }
html,body{ height:100%; margin:0; }
body{
  background:var(--bg); color:var(--ink);
  font:var(--t-base)/1.55 var(--f-sans);
  display:flex; flex-direction:column;
}

/* ---- boot overlay ---- */
.boot{ position:fixed; inset:0; z-index:50; background:var(--bg);
  display:grid; place-items:center; }
.boot.gone{ display:none; }
.boot-title{ font:600 var(--t-fine)/1 var(--f-mono); letter-spacing:.16em;
  text-transform:uppercase; color:var(--dim); margin-bottom:8px; }
.boot-msg{ color:var(--ink); max-width:44ch; text-align:center; }

/* ---- header. 46px: the tallest control is 28px, +16px of clearance, and
        42px starts to read as cramped. ---- */
.bar{ height:46px; flex:0 0 auto; display:flex; align-items:center; gap:12px;
  padding:0 12px; background:var(--bar); border-bottom:1px solid var(--line); }
.lock{ display:flex; align-items:center; gap:8px; color:var(--ink);
  text-decoration:none; border-radius:5px; }
/* The mark carries its own gradient fill, so it takes no colour from here —
   only the wordmark responds to hover, and it borrows the mark's accent so
   the lockup reads as one object rather than two. */
.lock svg{ display:block; }
/* Hover brightens the whole lockup. It cannot set `color`: the wordmark is
   painted by a clipped gradient, so its colour is transparent by design and
   changing it would do nothing. */
.lock:hover svg, .lock:hover .name{ filter:brightness(1.18); }
.lock:focus-visible{ outline:2px solid var(--brand); outline-offset:3px; }
/* The wordmark is an SVG (see index.html) rather than styled text, so it needs
   no font stack, no weight, and no background-clip trick — the fade is a real
   gradient fill. Width is intrinsic to the viewBox; only height is set. */
/* The mark is sized HERE and nowhere else — no height attribute on the svg,
   because a CSS height silently wins over one and the pair then disagree. Its
   viewBox is cropped to the ink, so 24px is the height actually drawn; on the
   old square box the path filled 53% of its height and 30px drew about 16px. */
.mark{ display:block; height:24px; width:auto; flex:0 0 auto; }

/* ---- the wordmark ----
   Impact, obliqued by the browser. font-style:italic is fine on a face with no
   italic — the synthesis is a shear and the strokes survive it. font-weight is
   NOT: Impact ships one weight, and faux-bold thickens by smearing, which
   closes its counters. Leave the weight alone.

   Tracking is positive, which is unusual for a heavy face: Impact is condensed
   enough that caps set solid start to knit together. 26px against the 24px
   mark balances because Impact's cap height is only ~0.72 of its font size. */
.name{
  font-family:Impact,Haettenschweiler,"Arial Black",ui-sans-serif,sans-serif;
  font-size:26px; font-style:italic; line-height:1;
  text-transform:uppercase; letter-spacing:.02em;
  color:var(--gold); white-space:nowrap; flex:0 0 auto;
}
.grow{ flex:1; }
/* View picker. A pressed tab means "on screen" — the same on/off vocabulary as
   a layer switch, because that is what it is. Named for what each view shows,
   never for what it costs. */
.views{ display:flex; gap:1px; padding:1px; border-radius:6px; background:#1a1e22;
  border:1px solid var(--line); }
.vtab{ height:26px; padding:0 12px; border:0; border-radius:5px; cursor:pointer;
  background:none; color:var(--dim); font:600 var(--t-body)/1 var(--f-sans); }
.vtab:hover:not(:disabled){ color:var(--ink); }
/* Dark text on gold, never white — see the note in :root. */
.vtab.on{ background:var(--gold); color:var(--bg); font-weight:700; }
.vtab:disabled{ opacity:.4; cursor:default; }
.vtab:focus-visible{ outline:2px solid var(--brand); outline-offset:2px; }
.btn{ height:28px; padding:0 12px; border-radius:5px; border:1px solid var(--line);
  background:#262b31; color:var(--ink); font:600 var(--t-body)/1 var(--f-sans);
  cursor:pointer; }
.btn:hover:not(:disabled){ background:#2e343b; }
.btn:disabled{ opacity:.45; cursor:default; }
/* The one call to action, in the one accent — the reference does exactly this:
   a single saturated gold button against black, everything else quiet. */
/* The CTA role. It was 13px/650 — the same voice as every other button — and
   the one action the whole app exists for should not whisper. */
.btn.primary{ background:var(--gold); border-color:var(--gold-deep);
  color:var(--bg); font:700 var(--t-base)/1 var(--f-sans);
  text-transform:uppercase; letter-spacing:.06em; height:34px; }
.btn.primary:hover:not(:disabled){ background:var(--gold-lift); }

/* ---- layout ---- */
main{ flex:1; display:grid; grid-template-columns:380px 1fr; min-height:0; }
.stage{ position:relative; display:grid; grid-template-columns:1fr 1px 1fr;
  border-left:1px solid var(--line); overflow:hidden; min-height:0; }

/* Splitter. The track stays 1px so the rule reads as a hairline; the grab
   area is widened by a pseudo-element overhanging its neighbours, which costs
   no layout. z-index is required — without it the overhang lands UNDER the
   pane content and the handle only responds on the literal single pixel.
   .h is the row divider in the two-row arrangements; same element, other
   axis, so its cursor and overhang flip. */
.split{ position:relative; z-index:5; background:var(--line); cursor:col-resize;
  touch-action:none; }
.split::before{ content:''; position:absolute; top:0; bottom:0; left:-3px; right:-3px; }
.split.h{ cursor:row-resize; }
.split.h::before{ top:-3px; bottom:-3px; left:0; right:0; }
.split:hover, .split.drag{ background:var(--brand); }
.split:focus-visible{ outline:2px solid var(--brand); }
.pane{ position:relative; overflow:hidden; background:var(--ground); min-width:0; }
.pane canvas{ display:block; width:100%; height:100%; }
/* The caption names which pane is which. A viewport that shows a shoe and a
   viewport that shows lines are not self-describing when both are empty. */
.pane-cap{ position:absolute; left:10px; top:9px; z-index:2; pointer-events:none;
  font:600 var(--t-label)/1 var(--f-mono); letter-spacing:.14em; text-transform:uppercase;
  color:#0d0f11; background:#ffffff5c; padding:4px 7px; border-radius:4px; }

/* ---- the drop cards ----
   The first thing anyone touches, so they carry the most type hierarchy in
   the app: icon, LEAD title, BODY sentence, LABEL format chips, then the
   secondary path. The dashed edge is the one universal "drop here" signal and
   is kept, at a weight that actually reads against the mid-grey canvas — the
   old 1px of --line measured 1.9:1 there and was nearly invisible.

   The whole PANE is the drop target, not this card; the card is only where
   the invitation is written. Hence the "or" divider: two real paths, drag or
   browse, and a card that showed only a button taught the wrong one. */
.slot{ position:absolute; inset:0; display:grid; place-items:center; padding:18px; z-index:3; }
.slot.hide{ display:none; }
.slot-inner{ width:min(330px,90%); text-align:center;
  /* The dash reads against the CARD, not the canvas: the background paints
     under the border, so the gaps show card colour. #566069 measured 2.8:1
     there — under the 3:1 floor for non-text UI — and the dashes went soft. */
  background:#171a1ef5; border:1.5px dashed #6a747e; border-radius:14px;
  padding:24px 24px 22px;
  box-shadow:0 12px 34px #00000047;
  transition:border-color .15s ease, background .15s ease; }
/* The icon carries the accent AT REST, muted. That does not break the rule
   that gold means interactive — the card IS the interactive object, the whole
   pane being the drop target — and intensity then does the state work:
   two-thirds strength waiting, full gold under a drag. */
.slot-icon{ color:var(--gold); opacity:.62; margin:0 auto 13px; width:38px; height:38px;
  transition:opacity .15s ease; }
.pane.over .slot-icon{ opacity:1; }
.slot-icon svg{ display:block; width:100%; height:100%; }
.slot-title{ font:700 var(--t-lead)/1.25 var(--f-sans); margin-bottom:7px;
  color:#fff; letter-spacing:-.012em; }
.slot-body{ color:var(--dim); margin:0 0 14px; font:var(--t-body)/1.5 var(--f-sans); }
.slot-body b{ color:var(--ink); font-weight:600; }

/* Format chips rather than a dot-separated run: three short tokens read as a
   set to scan, where "·"-joined text reads as a sentence to parse. */
.slot-fmt{ display:flex; justify-content:center; flex-wrap:wrap; gap:5px; margin-bottom:15px; }
.fmt{ padding:3px 7px; border:1px solid var(--line); border-radius:4px;
  background:#20242a; color:var(--dim);
  font:600 var(--t-label)/1 var(--f-mono); letter-spacing:.1em; }

.slot-or{ display:flex; align-items:center; gap:9px; margin:0 0 13px; color:var(--dim);
  font:var(--t-fine)/1 var(--f-sans); }
.slot-or::before, .slot-or::after{ content:''; flex:1; height:1px; background:var(--line); }
.slot-btn{ height:30px; padding:0 16px; }

/* Drop state: gold and solid. The card is the only thing that changes, so the
   feedback lands where the eye already is. */
.pane.over .slot-inner{ border-color:var(--gold); border-style:solid;
  background:#221f16f7; }
/* (the icon's over-state is opacity, set with .slot-icon above) */
/* A pane that already holds content still accepts a replacement drop, so it
   needs the same feedback without the panel coming back. */
.pane.over::after{ content:''; position:absolute; inset:0; z-index:2;
  border:2px solid var(--brand); pointer-events:none; }

/* Read-once furniture: essential on an empty canvas, clutter over a result.
   Proximity is measured in JS — an element with pointer-events:none is not a
   hit-test target and so never matches :hover, which is why the obvious
   CSS-only version cannot work. */
.hint{ position:absolute; left:10px; bottom:9px; padding:5px 9px; z-index:2;
  border-radius:6px; background:#121417aa; backdrop-filter:blur(6px);
  color:var(--ink); font-size:var(--t-fine); pointer-events:none;
  opacity:0; transition:opacity .18s ease; }
.hint.show{ opacity:1; }

/* Progress goes OVER the work, not in a status bar: a thin line under small
   text at the bottom of the window is routinely missed on a run lasting
   minutes, which is exactly when someone needs to know it is alive. */
.prog{ position:absolute; left:50%; top:18px; transform:translateX(-50%);
  min-width:260px; padding:10px 12px; border-radius:8px;
  background:#121417dd; backdrop-filter:blur(6px); border:1px solid var(--line); }
.prog-row{ display:flex; justify-content:space-between; gap:16px;
  font-size:var(--t-fine); margin-bottom:6px; }
#prog-pct{ font-variant-numeric:tabular-nums; color:var(--dim); }
.prog-track{ height:4px; border-radius:2px; background:#2a2f35; overflow:hidden; }
.prog-fill{ height:100%; width:0%; background:var(--brand); transition:width .15s; }

/* ---- panel ---- */
.panel{ background:var(--panel); padding:16px 18px 20px; overflow:auto;
  display:flex; flex-direction:column; }
.big{ font:800 var(--t-huge)/1 var(--f-sans); letter-spacing:-.03em;
  font-variant-numeric:tabular-nums; }
.verdict{ color:var(--dim); margin:6px 0 18px; font-size:var(--t-body); }
/* #verdict is the one sentence that states the answer, and it was set in the
   same grey as every hint in the panel — the most important line rendered as
   the quietest. It now carries the verdict's own colour and weight, the same
   red/blue as the meter and the two stat rows.
   #status keeps the plain grey: it gives instructions ("Load both files"),
   it does not report a result, which is why the colour goes on the id and
   not on the shared .verdict class. */
#verdict{ font-weight:600; }
#verdict.over{ color:var(--red); }
#verdict.under{ color:var(--blue); }

/* Each numbered step gets a rule running out to the panel edge. The panel is
   a sequence — files, settings, measure, result — and without a divider the
   four blocks ran together into one column of text. */
.panel h2{ font:600 var(--t-fine)/1 var(--f-mono); letter-spacing:.14em;
  text-transform:uppercase; color:var(--dim); margin:14px 0 10px;
  display:flex; align-items:center; gap:10px; }
.panel h2::after{ content:""; flex:1; height:1px; background:var(--line); }
/* A settings row is a control, so it is built like one: a padded row that
   lights up under the pointer and when anything inside it takes focus. Before
   this the label floated in dead space and only the 28px stepper looked
   touchable — the row now reads as one object from label to unit. */
.field{ display:flex; align-items:center; gap:8px; margin-bottom:4px;
  font-size:var(--t-body); padding:5px 8px; margin-left:-8px; margin-right:-8px;
  border-radius:8px; transition:background .12s ease; }
.field:hover, .field:focus-within{ background:#20242a; }
.field > label{ flex:1; cursor:default; }
.field em{ color:var(--dim); font-style:normal; font-size:var(--t-fine); width:20px; }
/* ---- number stepper ----
   The native spinner is two ~8px arrows stacked inside the field's right edge,
   which is far under any sane hit target and sits so close to the digits that
   aiming at one risks clicking into the text. Both arrows are hidden and
   replaced by a −/+ pair at 28px, the same height as every other control here,
   with the value between them.

   The buttons are NOT inside the label element (the field is a div with an
   explicit <label for>): a button inside a label re-triggers the label's own
   activation, which fires each step twice. */
.field input[type=number]{ -moz-appearance:textfield; appearance:textfield; }
.field input::-webkit-outer-spin-button,
.field input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }

.stepper{ display:flex; align-items:stretch; flex:0 0 auto;
  border:1px solid var(--line); border-radius:7px; overflow:hidden;
  background:#14171a; }
.stepper:focus-within{ border-color:var(--brand); }
.field input{ width:64px; height:28px; padding:0 4px; text-align:center;
  border:0; background:none; color:var(--ink);
  font:600 var(--t-body)/1 var(--f-mono); font-variant-numeric:tabular-nums; }
.field input:focus{ outline:none; }

.step{ width:28px; height:28px; flex:0 0 auto; display:grid; place-items:center;
  padding:0; border:0; background:#20242a; color:var(--ink);
  font:600 15px/1 var(--f-sans); cursor:pointer; user-select:none;
  touch-action:manipulation; }
.step:hover{ background:#2c323a; color:var(--gold); }
.step:active{ background:var(--gold); color:var(--bg); }
.step:focus-visible{ outline:2px solid var(--brand); outline-offset:-2px; }
.report{ margin:0; font:var(--t-fine)/1.5 var(--f-mono); color:var(--ink);
  white-space:pre; overflow-x:auto; }

/* Restates a setting in the unit the measurement actually uses, so a typed
   inch value can be checked against the statutory 6.35 mm without arithmetic. */
.note{ color:var(--dim); font:var(--t-fine)/1.4 var(--f-mono); margin:2px 0 0; }

@media (max-width:820px){
  main{ grid-template-columns:1fr; grid-template-rows:minmax(240px,1fr) auto; }
  /* The panel is first in the DOM so it lands on the left of a wide window;
     stacked, the views should still be on top, hence the explicit order. */
  .stage{ order:1; border-left:0; border-bottom:1px solid var(--line); }
  .panel{ order:2; }
}
@media (hover:none){ .hint{ opacity:1; } }

/* ---- viewports ---------------------------------------------------------
   The template above is only what the grid looks like before script runs.
   js/ui/layout.js owns it from then on — it writes the columns from which
   panes are visible and what the splitters have been dragged to, so do NOT
   add a competing rule here or the two will fight over the same property. */

/* The animation's narration line. Same glass as the other viewport furniture;
   centred over the bottom because the action is in the middle of the pane and
   captions read from under the picture. min-height holds one line so the box
   does not jump between acts of different caption lengths. */
.anim-cap{ position:absolute; left:50%; bottom:34px; transform:translateX(-50%);
  z-index:2; max-width:min(46ch, 88%); text-align:center; pointer-events:none;
  padding:6px 11px; border-radius:7px; background:#121417cc; backdrop-filter:blur(6px);
  color:var(--ink); font-size:var(--t-fine); line-height:1.5; min-height:1.5em;
  font-variant-numeric:tabular-nums; }

.legend{ position:absolute; right:10px; top:9px; z-index:2; display:grid; gap:3px;
  padding:6px 8px; border-radius:6px; background:#121417cc; backdrop-filter:blur(6px);
  font-size:var(--t-fine); color:var(--ink); pointer-events:none; }
.legend i{ display:inline-block; width:9px; height:9px; border-radius:2px;
  margin-right:6px; vertical-align:middle; }

/* ---- file readiness --------------------------------------------------- */
.files{ list-style:none; margin:0 0 6px; padding:0; }
.file{ display:flex; gap:9px; align-items:flex-start; padding:9px 10px; margin-bottom:6px;
  border:1px solid var(--line); border-radius:7px; background:#16191d;
  font-size:var(--t-body); }
.file.ready{ border-color:#2f6b45; background:#15201a; }
/* Icon left, state right: the row reads WHAT it is, then what it holds, then
   WHETHER it is ready. The glyph is the same one its drop card uses, so a row
   and its card are visibly the same object seen in two places. */
.file-ico{ flex:0 0 auto; width:19px; height:19px; margin-top:1px; color:#6d7883; }
.file-ico svg{ display:block; width:100%; height:100%; }
.file.ready .file-ico{ color:#5fd08a; }
.tick{ margin-left:auto; align-self:center;
  font:var(--t-body)/1 var(--f-mono); color:var(--dim); }
.file.ready .tick{ color:#5fd08a; }
.file-t{ display:flex; flex-direction:column; font-weight:600; }
.file-t em{ font-style:normal; font-size:var(--t-fine); color:var(--dim); margin-top:1px;
  overflow-wrap:anywhere; }

/* ---- result as UI, not prose ------------------------------------------ */
/* The card itself is defined further down, next to .panel h2 — see the note
   there. Do not restate it here: at equal specificity the later rule wins, so
   a second .result block up here is dead CSS that still reads as live. */
.meter{ position:relative; margin:12px 0 22px; }
.meter-track{ height:7px; border-radius:4px; background:#2a2f35; overflow:hidden; }
.meter-fill{ height:100%; width:0%; background:var(--blue); transition:width .3s ease; }
.meter.over .meter-fill{ background:var(--red); }
/* The 40% line, drawn AND named where the decision actually is. The tick was
   previously unlabelled with the figure only in a title attribute, which is
   invisible on touch and to anyone not hunting for a tooltip — on a meter
   whose entire purpose is one threshold. translateX(-50%) centres both the
   2px tick and the label on the 40% point rather than starting them there. */
   Do NOT fade the tick with `opacity` now that it has a child: group opacity
   multiplies down and is clamped at 1, so the label could not be brought back
   to full and would silently inherit the fade. The softening is baked into
   the colour instead, which affects only the element it is set on. */
.meter-mark{ position:absolute; left:40%; top:-3px; width:2px; height:13px;
  background:#c8ced4; transform:translateX(-50%); }
.meter-mark span{ position:absolute; top:15px; left:50%; transform:translateX(-50%);
  font:600 var(--t-label)/1 var(--f-mono); letter-spacing:.08em;
  color:var(--dim); }
.stats{ margin:0; display:grid; gap:0; }
.stats div{ display:flex; justify-content:space-between; gap:12px;
  padding:6px 0; border-bottom:1px solid var(--line); }
.stats div:last-child{ border-bottom:0; }
.stats dt{ color:var(--dim); font-size:var(--t-body); }
/* The measurement's own colours, on the two rows that ARE those lines.
   Measured against the RESULT CARD (#16191d), which is what actually paints
   behind them — not the panel: red 5.8:1, blue 8.8:1. Both clear the 4.5:1
   floor for text this size, so the tie-in costs no legibility. */
.stats .is-above dd{ color:var(--red); }
.stats .is-below dd{ color:var(--blue); }
.stats dd{ margin:0; font:600 var(--t-body)/1.3 var(--f-mono);
  font-variant-numeric:tabular-nums; text-align:right; }

/* ---- report log ------------------------------------------------------- */
.log{ flex:0 0 auto; border-top:1px solid var(--line); background:var(--bar); }
.log-bar{ width:100%; display:flex; align-items:center; gap:12px; height:32px;
  padding:0 12px; background:none; border:0; color:var(--dim); cursor:pointer;
  font:600 var(--t-label)/1 var(--f-mono); letter-spacing:.14em; text-transform:uppercase; }
.log-bar:hover{ color:var(--ink); }
.log-t{ color:inherit; }
.log-hint{ flex:1; text-align:left; letter-spacing:0; text-transform:none;
  font:var(--t-fine)/1 var(--f-sans); color:var(--dim); }
.log-chev{ transition:transform .18s ease; }
.log.open .log-chev{ transform:rotate(180deg); }
/* ---- the two readouts ----
   A grid rather than one scroller, because each pane must scroll on its OWN:
   the report is read from the top and the log follows its tail, and a single
   shared scrollbar cannot do both. Same split vocabulary as the stage above —
   two panes with a 1px rule between them.

   The modifier is `two-up`, NOT `split`: `.split` is already the stage's
   draggable divider, and reusing the name handed this panel that element's
   entire appearance — a col-resize cursor and a gold `:hover` fill across
   the whole readout. Class names are a flat namespace; a modifier that reads
   naturally is still a collision if a component already owns it. */
.log-body{ margin:0; display:grid; grid-template-columns:1fr;
  max-height:230px; border-top:1px solid var(--line); }
/* `[hidden]` is only `display:none` in the UA stylesheet, so ANY author
   `display` outranks it and the attribute silently stops hiding. Setting the
   grid above did exactly that: the collapse button set `hidden` and the panel
   stayed on screen. Any rule that gives an element a display value must
   restate this. */
.log-body[hidden]{ display:none; }
.log-body.two-up{ grid-template-columns:1fr 1px 1fr; }
.logsplit{ background:var(--line); }
.logpane{ display:flex; flex-direction:column; min-width:0; overflow:hidden; }
/* The caption earns its place only when two panes need telling apart; with
   one open the tab above already names it. */
.logpane-cap{ flex:0 0 auto; margin:0; padding:7px 12px 5px;
  font:600 var(--t-label)/1 var(--f-mono); letter-spacing:.14em;
  text-transform:uppercase; color:var(--dim); }
.log-body:not(.two-up) .logpane-cap{ display:none; }
.logpane-text{ flex:1; margin:0; overflow:auto; padding:3px 12px 12px;
  font:var(--t-fine)/1.5 var(--f-mono); color:var(--ink); white-space:pre; }
.log-body:not(.two-up) .logpane-text{ padding-top:10px; }

/* ---- panel actions and polish ----------------------------------------- */
.actions{ display:grid; gap:8px; margin:2px 0 6px; }
.btn.wide{ width:100%; height:32px; }
/* At equal specificity source order decides, and this rule sits after
   .btn.primary — without the restatement, .wide's 32px silently capped the
   CTA's 34px. Keep this AFTER .btn.wide. */
.btn.primary.wide{ height:34px; }
/* The result reads as a card so the answer has a visible home, not a stretch
   of loose text. Same ink, one hairline.
   The left edge carries the verdict's colour — the same red/blue as the
   sentence, the meter and the two stat rows — so the block is readable as
   over/under from the corner of the eye, before any digit is read. */
.result{ border:1px solid var(--line); border-radius:9px; padding:14px 15px 10px;
  background:#16191d; margin:2px 0 8px;
  border-left:3px solid var(--blue); }
.result:has(#verdict.over){ border-left-color:var(--red); }
.panel h2{ margin:18px 0 10px; }
.panel h2:first-of-type{ margin-top:2px; }

/* ---- report / log tabs ------------------------------------------------- */
.log-bar{ display:flex; align-items:center; gap:8px; height:34px; padding:0 8px 0 4px; }
.log-tab{ height:24px; padding:0 12px; border:1px solid transparent; border-radius:5px;
  background:none; color:var(--dim); cursor:pointer;
  font:600 var(--t-label)/1 var(--f-mono); letter-spacing:.14em; text-transform:uppercase; }
.log-tab:hover{ color:var(--ink); }
/* Gold TEXT, not a gold fill. These switch a secondary panel, so they share
   the accent's language without competing with the one filled gold button —
   spend the boldness in one place and keep everything around it quiet. */
.log-tab.active{ color:var(--gold); background:#262b31; border-color:var(--line); }
/* Collapsed, nothing is on screen, so a lit tab would be claiming to show
   something it is not. The tab keeps its aria-pressed state — that is about
   which readout is SELECTED, and clicking it while collapsed opens exactly
   that one — but it drops the lit appearance, which is about what is visible. */
.log:not(.open) .log-tab.active{ color:var(--dim); background:none;
  border-color:transparent; }
.log:not(.open) .log-tab:hover{ color:var(--ink); }
/* Framed, so it reads as a control rather than a stray glyph. Only the CHEVRON
   rotates, not the button: rotating the button would spin its frame too, and a
   square frame tumbling 180° is visible even when the shape is symmetrical. */
.log-toggle{ display:grid; place-items:center; width:24px; height:24px; flex:0 0 auto;
  padding:0; background:#262b31; border:1px solid var(--line); border-radius:5px;
  color:var(--dim); cursor:pointer; font:var(--t-fine)/1 var(--f-sans); }
.log-toggle:hover{ color:var(--ink); background:#2e343b; }
.log-toggle:focus-visible{ outline:2px solid var(--brand); outline-offset:2px; }
.log-toggle svg{ transition:transform .18s ease; }
.log.open .log-toggle svg{ transform:rotate(180deg); }

/* Visually hidden but read aloud: the button's only visible content is an
   arrow, which announces as nothing without this. */
.sr{ position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }

/* ---- runs-locally note ----
   Bottom right, one line, expanding upward into a panel that is absolutely
   positioned and therefore costs no layout at all. The bridge that keeps the
   hover alive while the pointer crosses the gap lives on the CONTAINER, never
   on the panel: an absolutely positioned descendant counts toward a scrolling
   box's scrollHeight and would invent a scrollbar on content that fits. */
/* Framed like the log toggle beside it: it is operated (hover or focus opens
   the panel), and an unframed icon-plus-caps cluster read as decoration. */
.local{ position:relative; flex:0 0 auto; display:flex; align-items:center; gap:6px;
  margin-left:auto; height:24px; padding:0 9px; border-radius:5px;
  background:#262b31; border:1px solid var(--line); cursor:default;
  color:var(--dim); letter-spacing:.1em; }
.local:hover{ background:#2e343b; }
.local svg{ display:block; }
/* Status LED. Green is the app's existing "ready" colour (the file ticks use
   it), so a reader who has seen a file go green already knows what it means.
   It is NOT the gold accent: gold means interactive here, and this reports
   state rather than inviting a click. */
.local-dot{ width:6px; height:6px; border-radius:50%; background:#5fd08a;
  box-shadow:0 0 0 2px #5fd08a26; flex:0 0 auto; }
.local:hover, .local:focus-within{ color:var(--gold); }
.local:focus-visible{ outline:2px solid var(--brand); outline-offset:2px; }
.local::after{ content:''; position:absolute; left:0; right:0; bottom:100%; height:10px; }

.local-pop{ position:absolute; right:0; bottom:calc(100% + 8px); width:300px;
  padding:11px 13px; border:1px solid var(--line); border-radius:8px;
  background:#14171acc; backdrop-filter:blur(8px); color:var(--ink);
  font:var(--t-fine)/1.55 var(--f-sans);
  letter-spacing:0; text-transform:none; text-align:left;
  opacity:0; visibility:hidden; transform:translateY(3px);
  transition:opacity .16s ease, transform .16s ease, visibility .16s;
  pointer-events:none; z-index:20; }
.local:hover .local-pop, .local:focus-within .local-pop{
  opacity:1; visibility:visible; transform:translateY(0); }
/* The maker's plate: provenance stamped, not written — the print on the back
   of the device. ONE grey for every part including the links; a brighter link
   colour cuts a single stamp into pieces and turns it into a sentence with
   hyperlinks in it. Hover/focus reveal the links, the cursor does the rest.
   Tracking is TIGHTER than the headings it borrows (.08em vs .14em): these
   are the longest lines in the panel and the wide setting overflows. */
.plate{ display:block; margin-top:11px; padding-top:10px;
  border-top:1px solid var(--line); text-align:center; color:var(--dim);
  font:600 var(--t-label)/1.9 var(--f-mono);
  letter-spacing:.08em; text-transform:uppercase; }
.plate a{ color:inherit; text-decoration:none; cursor:pointer; }
.plate a:hover, .plate a:focus-visible{ color:var(--ink); }

/* On touch there is nothing to hover with, so a panel that only opens on
   hover is simply missing. Show it inline instead. */
@media (hover:none){
  .local-pop{ position:static; opacity:1; visibility:visible; transform:none;
    width:auto; border:0; background:none; padding:0 0 0 4px; backdrop-filter:none; }
}
