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

/* ═══════════════════════════════════════════════════════════════════════
   GRIMOIRE — foundation

   This application exists to do two things, and the design answers to both:

     1. SHOW WHAT NEEDS YOU.  An alert must be unmissable.
     2. LET YOU READ THE MACHINES.  Following your own figures is not a
        failure state, and the numbers you came for should be the brightest
        thing on a screen where nothing is wrong.

   Those pull in opposite directions only if they share a colour. They do
   not. TWO SEPARATE AXES:

     STATE     rubric / amber / green. Reserved absolutely. Nothing that is
               merely important is ever allowed to borrow one.
     EMPHASIS  cyan, and the ink scale. Marks the READING as against its
               label and units — prominence, not alarm. Free to use often,
               because it can never be mistaken for a warning.

   An earlier pass here was too muted and too flat: panels sat two percent
   off the page so nothing read as an object, and controls were bordered
   identically to labels so a row of choices looked like a row of text.
   Both are fixed below with contrast rather than with decoration.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Ground ──────────────────────────────────────────────────────────
     Cold near-black. Each step is a real step — a panel must read as an
     object sitting on the page, not as a shade of it. */
  --bg:            #0E1216;
  --bg-raised:     #161C22;   /* panels */
  --bg-alt:        #131920;   /* zebra rows — the biggest single win on a
                                 wall of monospace */
  --bg-sunk:       #0A0E12;   /* wells, tracks, control groups, inputs */

  --rule:          #212A33;
  --rule-strong:   #33404C;

  /* ── Ink ─────────────────────────────────────────────────────────── */
  --text:          #E9EFF5;
  --text-dim:      #A8B6C2;
  --text-faint:    #6B7C8A;

  /* ── State ───────────────────────────────────────────────────────────
     Reserved. If something is merely important, it does not get one. */
  --rubric:        #F2585B;   /* act now, or destructive */
  --ochre:         #F0B429;   /* attention, degraded, near a threshold */
  --verdigris:     #3DD68C;   /* confirmed good — verified, passed, clean */

  --rubric-wash:   rgba(242, 88, 91, .10);
  --ochre-wash:    rgba(240, 180, 41, .10);
  --lapis-wash:    rgba(79, 182, 220, .10);

  /* ── Emphasis ────────────────────────────────────────────────────────
     Cyan. Interactive controls AND the figure a panel exists to show.
     Both are "look here"; neither is "something is wrong". Used freely on
     purpose — this is the colour that makes specs worth reading. */
  --lapis:         #4FB6DC;
  --lapis-bright:  #8FD3EA;
  --lapis-deep:    #2C7E9E;

  /* Quantity: bar and strip fills. Magnitude, not state — threshold
     colours override it, at which point it has become a state. */
  --ink-bar:       #4FB6DC;
  --ink-bar-dim:   #2C5468;

  /* Progress — the site tree only. Neither state nor quantity. */
  --gold:          #C9A227;
  --gold-bright:   #EFD98A;
  --gold-dim:      #40371A;

  /* ── Type ────────────────────────────────────────────────────────────
     Two faces. Space Grotesk carries the identity through its squared
     terminals rather than through a book serif — the previous pairing put
     a display serif to work labelling diagrams, which is what made the old
     design read as costume. IBM Plex Mono takes EVERY numeral, so digits
     align down a column and stop jittering as they update.

     --serif is kept as a name so no page has to be edited; it resolves to
     the same family. */
  --sans:          'Space Grotesk', system-ui, sans-serif;
  --serif:         'Space Grotesk', system-ui, sans-serif;
  --mono:          'IBM Plex Mono', ui-monospace, monospace;

  /* ── Scale ───────────────────────────────────────────────────────────
     Every step is up roughly 1–2px on the previous pass, which read as
     cramped. A dashboard is scanned from a metre away as often as it is
     read from forty centimetres. */
  --t-xs:          11.5px;
  --t-sm:          13px;
  --t-base:        14.5px;
  --t-md:          17.5px;
  --t-lg:          24px;
  --t-xl:          34px;

  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;
  --radius:        3px;
  --radius-lg:     4px;
  --measure:       72ch;
}

/* ═══ Reset ═══════════════════════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: var(--t-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.num, code, kbd, pre, input[type="number"] {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ═══ Type roles ══════════════════════════════════════════════════════ */
.title {
  font-family: var(--sans);
  font-size: var(--t-lg);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.15;
}

.heading {
  font-family: var(--sans);
  font-size: var(--t-md);
  font-weight: 600;
  letter-spacing: -.01em;
}

.eyebrow {
  font-family: var(--sans);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--text-faint);
  line-height: 1;
}

/* Marginal note — the explanations that would be a footnote in print. */
.gloss {
  font-size: var(--t-sm);
  color: var(--text-faint);
  max-width: var(--measure);
  line-height: 1.6;
}

/* Units are never the reading. Smaller, dimmer, always. */
.unit { font-size: .68em; color: var(--text-faint); letter-spacing: .02em; font-weight: 400; }

/* ── Emphasis ──────────────────────────────────────────────────────────
   THE reading. The figure a panel was built to show, as against everything
   around it. Cyan and heavier — this is what makes a spec worth following,
   and it is safe to use often precisely because it is not a state colour. */
.figure {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--lapis-bright);
  font-weight: 500;
}
.figure.lg { font-size: var(--t-xl); line-height: 1.1; }
.figure.md { font-size: var(--t-lg); line-height: 1.15; }

/* State always outranks emphasis: the moment a figure crosses a line it
   stops being interesting and starts being a problem. */
.figure.is-warn { color: var(--rubric); }
.figure.is-caut { color: var(--ochre); }
.figure.is-good { color: var(--verdigris); }

/* ═══ Structure ═══════════════════════════════════════════════════════ */
.section { border-top: 1px solid var(--rule-strong); padding-top: var(--s4); }
.section + .section { margin-top: var(--s6); }
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s4); margin-bottom: var(--s4);
}
.row { border-top: 1px solid var(--rule); padding: var(--s3) 0; }
.row:first-child { border-top: 0; }

/* A panel is an object on the page, not a tint of it. */
.panel {
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: var(--s3) var(--s4) var(--s4);
  min-width: 0;
}
.panel-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s3); padding-bottom: var(--s2); margin-bottom: var(--s3);
  border-bottom: 1px solid var(--rule);
}
.panel-head .note {
  font-family: var(--mono); font-size: var(--t-xs);
  color: var(--text-faint); letter-spacing: .04em; white-space: nowrap;
}

/* A proportion, shown as one. An encoding, not ornament: a bar reports
   fullness before the digits have been read. */
.track {
  height: 6px;
  background: var(--bg-sunk);
  border-radius: 3px;
  overflow: hidden;
}
.track > i { display: block; height: 100%; background: var(--ink-bar); border-radius: 3px; }
.track > i.over { background: var(--ochre); }
.track > i.crit { background: var(--rubric); }

/* ═══ State classes ═══════════════════════════════════════════════════ */
.is-warn  { color: var(--rubric); }
.is-caut  { color: var(--ochre); }
.is-good  { color: var(--verdigris); }
.is-quiet { color: var(--text-faint); }

.marked { border-left: 3px solid var(--rule-strong); padding-left: var(--s3); }
.marked.is-warn { border-left-color: var(--rubric); background: var(--rubric-wash); }
.marked.is-caut { border-left-color: var(--ochre); }
.marked.is-good { border-left-color: var(--verdigris); }

/* ═══ Controls ════════════════════════════════════════════════════════ */
.field { display: flex; flex-direction: column; gap: var(--s2); }

/* Inputs are wells, not underlines. A ruled field was elegant and read as
   a caption; a control has to look like something you can put a value in. */
.field-input {
  background: var(--bg-sunk);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--t-base);
  padding: var(--s2) var(--s3);
  width: 100%;
  transition: border-color .12s;
}
.field-input::placeholder { color: var(--text-faint); }
.field-input:focus { outline: none; border-color: var(--lapis); }
.field-input:disabled { color: var(--text-faint); cursor: not-allowed; }

.btn {
  font-family: var(--sans);
  font-size: var(--t-sm);
  font-weight: 700;
  letter-spacing: .02em;
  padding: var(--s2) var(--s4);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--bg-sunk);
  color: var(--text);
  cursor: pointer;
  transition: border-color .12s, background .12s, color .12s;
}
.btn:hover:not(:disabled) { border-color: var(--lapis); color: var(--lapis-bright); }
.btn:disabled { opacity: .4; cursor: not-allowed; }

.btn-primary {
  background: var(--lapis); border-color: var(--lapis); color: var(--bg);
}
.btn-primary:hover:not(:disabled) { background: var(--lapis-bright); border-color: var(--lapis-bright); color: var(--bg); }

.btn-danger { border-color: var(--rubric); color: var(--rubric); background: none; }
.btn-danger:hover:not(:disabled) { background: var(--rubric-wash); }

:focus-visible { outline: 2px solid var(--lapis); outline-offset: 2px; }

a { color: var(--lapis); text-decoration: none; }
a:hover { color: var(--lapis-bright); text-decoration: underline; text-underline-offset: 3px; }

/* ═══ Tables ══════════════════════════════════════════════════════════ */
.table { width: 100%; border-collapse: collapse; }
.table th {
  font-family: var(--sans); font-size: var(--t-xs); font-weight: 700;
  letter-spacing: .15em; text-transform: uppercase; color: var(--text-faint);
  text-align: left; padding: 0 var(--s3) var(--s2); white-space: nowrap;
}
.table td {
  padding: var(--s2) var(--s3);
  border-top: 1px solid var(--rule);
  color: var(--text-dim);
  font-size: var(--t-sm);
}
.table td.num, .table th.num { text-align: right; }
.table tbody tr:hover td { background: var(--bg-raised); }

/* ═══ Motion ══════════════════════════════════════════════════════════
   Transitions belong on things the user caused. Nothing animates because
   data arrived — a value that slides into place is harder to read and
   implies a change that may not have happened. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}
