/* Operator console.

   Follows the machine. Light is the base palette; the dark one is the same
   token names redefined, so every component below reads from a variable and
   none of them know which scheme they are in. A colour defined only inside the
   dark block would render one theme's text on the other theme's background --
   that is the classic bug here, and the reason nothing outside these blocks
   names a colour.

   Three states, not two: an explicit choice stamps data-theme on <html>, and
   the default -- following the system -- stamps nothing, so only the media
   query separates day from night.

   The purple survives both. It is not decoration: it is how you know at a
   glance that you are in the global plane, looking across every customer,
   rather than inside one. */

:root {
  color-scheme: light dark;

  --bg: #f7f6fa;
  --surface: #ffffff;
  --surface-2: #f3f2f8;
  --surface-3: #e8e6f0;
  --line: #e1dfea;
  --line-soft: #eeecf4;

  --text: #1a1820;
  --muted: #6a677a;
  --faint: #918ea3;

  /* Taken from the mark: #5CCEF9 → #2E9BF0 → #1667D8. On a light ground the
     deep end is the only one with enough contrast to carry text. */
  --accent: oklch(0.53 0.18 255);
  --accent-strong: oklch(0.44 0.18 258);
  --accent-soft: oklch(0.53 0.18 255 / 0.10);
  /* The step callout. Its own token rather than --surface-2, which sits
     1% away from the page behind it and read as a rendering glitch. A
     tint of the accent, so the panel and the border down its left edge
     are visibly the same idea. */
  --callout: oklch(0.955 0.028 252);
  --on-accent: #ffffff;

  --ok: oklch(0.47 0.13 150);
  --ok-soft: oklch(0.62 0.13 150 / 0.14);
  --warn: oklch(0.48 0.12 70);
  --warn-soft: oklch(0.75 0.13 70 / 0.20);
  --danger: oklch(0.50 0.18 25);
  --danger-soft: oklch(0.62 0.18 25 / 0.12);

  /* Text on a filled warn background -- flips between the schemes, so it is a
     token rather than the literal it used to be. */
  --on-warn: #ffffff;

  --shadow-pop: 0 10px 28px rgba(26, 24, 32, 0.16);
  --shadow-card: 0 1px 2px rgba(26, 24, 32, 0.05);

  --radius: 6px;
  --radius-sm: 4px;
  --font-ui: "Public Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --fs-xs: 10.5px; --fs-sm: 11.5px; --fs-body: 12.5px; --fs-md: 13.5px;
}

/* Following the system. Guarded so an explicit light choice still wins on a
   machine that is in dark mode. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0f1014;
    --surface: #16181e;
    --surface-2: #131519;
    --surface-3: #242832;
    --line: #262a33;
    --line-soft: #1c1f26;

    --text: #e4e4ea;
    --muted: #8e909c;
    --faint: #63656f;

    --accent: oklch(0.76 0.13 240);
    --accent-strong: oklch(0.84 0.11 235);
    --accent-soft: oklch(0.76 0.13 240 / 0.14);
    /* See the light block: a tint of the accent, not another grey. */
    --callout: oklch(0.245 0.035 252);
    --on-accent: #04141f;

    --ok: oklch(0.80 0.12 150);
    --ok-soft: oklch(0.78 0.12 150 / 0.13);
    --warn: oklch(0.82 0.12 80);
    --warn-soft: oklch(0.78 0.12 80 / 0.14);
    --danger: oklch(0.76 0.16 25);
    --danger-soft: oklch(0.68 0.16 25 / 0.15);

    --on-warn: #0e1116;

    --shadow-pop: 0 10px 28px rgba(0, 0, 0, 0.5);
    --shadow-card: none;
  }
}

/* Pinned to dark, whatever the machine says. */
:root[data-theme="dark"] {
  --bg: #0f1014;
  --surface: #16181e;
  --surface-2: #131519;
  --surface-3: #242832;
  --line: #262a33;
  --line-soft: #1c1f26;

  --text: #e4e4ea;
  --muted: #8e909c;
  --faint: #63656f;

  --accent: oklch(0.76 0.13 240);
  --accent-strong: oklch(0.84 0.11 235);
  --accent-soft: oklch(0.76 0.13 240 / 0.14);
  /* See the light block: a tint of the accent, not another grey. */
  --callout: oklch(0.245 0.035 252);
  --on-accent: #04141f;

  --ok: oklch(0.80 0.12 150);
  --ok-soft: oklch(0.78 0.12 150 / 0.13);
  --warn: oklch(0.82 0.12 80);
  --warn-soft: oklch(0.78 0.12 80 / 0.14);
  --danger: oklch(0.76 0.16 25);
  --danger-soft: oklch(0.68 0.16 25 / 0.15);

  --on-warn: #0e1116;

  --shadow-pop: 0 10px 28px rgba(0, 0, 0, 0.5);
  --shadow-card: none;
}

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font-family: var(--font-ui); font-size: var(--fs-body); line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent-strong); text-decoration: none; }
a:hover { text-decoration: underline; }
.mono { font-family: var(--font-mono); }
.muted { color: var(--muted); }
.faint { color: var(--faint); }
.right { text-align: right; }
.grow { flex-grow: 1; }
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.stack { display: flex; flex-direction: column; gap: 12px; }

/* A checkbox with a sentence beside it. Not `.row`: that wraps, which puts
   the box on a line of its own above its own label. */
.check { display: flex; gap: 9px; align-items: flex-start; cursor: pointer; }

/* The gear beside a customer's name. Sits on the baseline of the heading
   rather than in the button row, because it configures the thing named to its
   left rather than being another place to go. */
.btn-gear {
  background: none; border: 0; padding: 3px; margin-left: 7px; cursor: pointer;
  color: var(--muted); vertical-align: middle; line-height: 0;
  border-radius: 6px;
}
.btn-gear:hover { color: var(--text); background: var(--surface-2); }

/* A section heading inside the settings dialog. */
.set-head {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 9px;
  padding-bottom: 6px; border-bottom: 1px solid var(--line-soft);
}

/* The onboarding road: circles beside the customer's name, and one panel that
   drops down when you click one.

   Selection is a radio group -- the inputs come first in the markup so `~`
   reaches both the circles in the header and the panels below -- so the whole
   thing works with JavaScript off. app.js adds one thing on top: clicking
   away closes the panel, which CSS cannot express and which is polish rather
   than function. */
.road { position: relative; }
.road-pick { position: absolute; opacity: 0; pointer-events: none; }

/* Label beside the circle, not under it: this sits on the title row, and a
   second line of text there would push the whole page down a band. */
.stepper { list-style: none; margin: 0; padding: 0; display: flex;
           align-items: center; flex: 1 1 auto; max-width: 62%; }
.stepper-step { display: flex; align-items: center; flex: 1 1 auto; min-width: 0; }
.stepper-step:last-child { flex: 0 0 auto; }
.stepper-step label {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 3px 5px; border-radius: 999px; min-width: 0;
}
.stepper-step label:hover { background: var(--surface-2); }

/* The connector is a flex child of the step, so it takes whatever room is
   left rather than needing a width nobody can predict. */
.stepper-step::after {
  content: ""; flex: 1 1 auto; min-width: 12px; height: 2px;
  background: var(--line); margin: 0 8px;
}
.stepper-step:last-child::after { display: none; }
/* Green only where *both* ends are done. These rungs do not complete in order
   -- records can arrive before Slack -- so a line coloured from one end would
   say a stretch was finished when it was not. */
.stepper-step.done:has(+ .stepper-step.done)::after { background: var(--ok); }

.stepper-dot {
  flex: none;
  width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: var(--fs-xs); font-weight: 700;
  background: var(--surface); color: var(--muted);
  border: 2px solid var(--line);
}
.stepper-step.done .stepper-dot {
  background: var(--ok); border-color: var(--ok); color: #fff;
}
.stepper-cap {
  font-size: var(--fs-xs); font-weight: 600; color: var(--muted);
  line-height: 1.3; white-space: nowrap;
}
.stepper-step.done .stepper-cap { color: var(--ok); }

/* The one thing on a page that is asking to be looked at. Deliberately the
   danger colour and nothing else -- a dot that is also a badge with a count is
   two claims, and this one only ever means "something that was working
   stopped". It goes out when somebody opens the step, so it cannot accumulate
   into wallpaper. */
.alert-dot {
  display: inline-block; width: 8px; height: 8px; margin-left: 5px;
  border-radius: 50%; background: var(--danger); vertical-align: middle;
  flex: none;
  /* A ring that grows and fades, rather than the dot itself moving. Something
     that changes size in a table makes the row twitch, and a row that twitches
     is harder to read than one with a coloured dot on it. */
  animation: alert-pulse 2.4s ease-out infinite;
}
@keyframes alert-pulse {
  0%   { box-shadow: 0 0 0 0 var(--danger-soft); }
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
/* It has done its job once you are hovering it, and a thing that keeps
   flashing while you read its tooltip is just noise. */
.alert-dot:hover { animation: none; }
@media (prefers-reduced-motion: reduce) {
  .alert-dot { animation: none; }
}
.stepper-dot .alert-dot {
  position: absolute; top: -1px; right: -1px; margin: 0;
  /* A ring in the page colour, so the dot reads as sitting on top of the
     circle rather than as part of its edge. */
  box-shadow: 0 0 0 2px var(--bg);
}
.stepper-dot { position: relative; }
/* Inline in a sentence rather than a form row: the line reads "Remove it in
   an hour — 28 Aug, 19:24", and a full-width control would break it in two. */
.field-inline { width: auto; max-width: 16rem; display: inline-block; }
/* Bottom-left, out of the way of everything: this interrupts nobody, it waits
   to be noticed. It is an offer to refresh, never a refresh. */
.live-note {
  position: fixed; left: 16px; bottom: 16px; z-index: 60;
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 10px;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--border);
  box-shadow: 0 6px 24px rgb(0 0 0 / 0.16);
  font-size: var(--fs-sm);
  animation: live-in 160ms ease-out;
}
@keyframes live-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .live-note { animation: none; } }
.disconnect { margin-top: 14px; }
.disconnect > summary {
  cursor: pointer; font-size: var(--fs-sm); color: var(--danger);
  padding: 4px 0; user-select: none;
}
.install-history { margin-top: 12px; }
.install-history > summary {
  cursor: pointer; font-size: var(--fs-sm); color: var(--muted);
  padding: 4px 0; user-select: none;
}
.install-history > summary:hover { color: var(--fg); }
.install-history .table-wrap { margin-top: 6px; }

.road-panels { position: relative; }
.road-panel {
  display: none; position: relative;
  border-left: 3px solid var(--accent);
  background: var(--callout);
  border-radius: 0 9px 9px 0; padding: 13px 16px; margin-bottom: 16px;
}
/* A tail pointing back at the circle this panel belongs to, so five identical
   panels are told apart by where they came from rather than by reading the
   heading. app.js measures the circle and sets --arrow-x; with no script there
   is simply no tail, and the heading still says which step it is. */
.road-panel::before {
  content: ""; position: absolute; bottom: 100%; left: var(--arrow-x, 28px);
  margin-left: -8px; border: 8px solid transparent;
  border-bottom-color: var(--callout);
}
.road-panel-title { font-weight: 700; margin-bottom: 5px; }
.road-panel-blurb {
  margin: 0; color: var(--muted); font-size: var(--fs-sm); line-height: 1.6;
}
.road-panel .btn { margin-top: 9px; }

/* The editable half of the first step's panel. Ruled off, because everything
   above it is something we found and everything below it is something you can
   change. */
.road-panel-edit {
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}
.road-panel-edit .field-label { margin-bottom: 3px; }
/* One column of rows inside a step's panel, each a field with its buttons
   beside it.

   Plain flex, not subgrid. Subgrid would let separate <form> rows share a
   column and align on their own, and it did -- in one browser. Where it is not
   supported the declaration is simply invalid, the form falls back to a single
   column, and every button drops onto a row of its own: the layout does not
   degrade, it inverts. A capped field width gets the same alignment out of
   rules every browser has had for a decade. */
.set-rows { display: flex; flex-direction: column; gap: 7px; }
.set-row { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }

/* Capped rather than filling the row: a company name and a domain are short,
   and a box stretched across a wide screen puts its Save button somewhere you
   have to go looking for. Every row caps at the same width, which is what
   makes the buttons line up without any of them knowing about each other. */
/* A length, not `ch`. The domain field is monospace and the rest is not, so a
   `ch` cap resolves to a different width per row and the boxes stop lining up
   the moment the cap actually binds. */
.set-row > .field,
.set-row > .set-static,
.set-row > .add-row { flex: 1 1 auto; max-width: 34rem; min-width: 0; }
/* Reserved, so every field is the same width whatever follows it. Without a
   fixed column each row's field shrinks by however many buttons it happens to
   have, and three rows end in three different places. */
.set-static {
  font-size: var(--fs-sm); align-self: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.set-actions {
  display: flex; align-items: center; justify-content: flex-end; gap: 6px;
  flex: none; min-width: 7.5rem;
}
/* A line of explanation with its "+" following the words rather than parked
   at the far right, where it reads as belonging to the row above. */
.set-note {
  font-size: var(--fs-xs); margin: 7px 0 1px;
  display: flex; align-items: center; gap: 8px;
}
.set-note .btn-add { flex: none; }

/* A value somebody has to paste somewhere else, with the button that does it.
   Selectable either way -- the button is a convenience, not the only route. */
.copy-row {
  display: flex; align-items: center; gap: 8px; margin: 6px 0 2px;
}
.copy-value {
  flex: 1 1 auto; min-width: 0; overflow-x: auto; white-space: nowrap;
  font-family: var(--font-mono); font-size: var(--fs-xs);
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: 7px; padding: 8px 10px;
}

/* The last row of a list, and the way you add to it. Same width as the fields
   above so the left and right edges line up -- a small square button beside a
   sentence reads as belonging to the sentence, not to the list. */
.add-row {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; max-width: 34rem;
  padding: 7px 10px; border-radius: var(--radius-sm);
  border: 1px dashed var(--line); background: none; cursor: pointer;
  color: var(--muted); font-size: var(--fs-sm); font-weight: 500;
}
/* The quick-peek dialog and what goes in it. */
.peek { max-width: 34rem; }
.peek [data-peek-body] { transition: opacity .12s ease; }
/* A small table inside the dialog. Not `.table-wrap`: that one is built for a
   page-width grid with a minimum width, and inside a 34rem dialog its minimum
   is what you would be scrolling. */
.peek-list { margin-top: 14px; }
.peek-list-head, .peek-list-row {
  display: grid; grid-template-columns: minmax(0,1fr) 64px 74px 72px;
  gap: 10px; align-items: center; padding: 7px 0;
}
.peek-list-head.three, .peek-list-row.three {
  grid-template-columns: minmax(0,1fr) 74px 72px;
}
.peek-list-head {
  font-size: var(--fs-xs); color: var(--faint); text-transform: uppercase;
  letter-spacing: .04em; border-bottom: 1px solid var(--line-soft);
}
.peek-list-row + .peek-list-row { border-top: 1px solid var(--line-soft); }

/* The thread inside the dialog. Scrolls on its own so a long conversation does
   not push the state out of view -- the state is what you clicked to see. */
.peek-thread {
  margin-top: 13px; max-height: 22rem; overflow-y: auto;
  display: flex; flex-direction: column; gap: 11px;
}
.peek-msg { padding-left: 11px; border-left: 3px solid var(--line); }
.peek-msg.out { border-left-color: var(--accent); }
.peek-msg-text { white-space: pre-wrap; margin-top: 3px; font-size: var(--fs-sm); line-height: 1.55; }

.peek-danger {
  margin-top: 16px; padding-top: 13px; border-top: 1px solid var(--line-soft);
}
.peek-head {
  display: flex; align-items: flex-start; gap: 12px; justify-content: space-between;
  padding-bottom: 11px; border-bottom: 1px solid var(--line-soft);
}
.peek-title { font-size: var(--fs-md); font-weight: 700; }
.peek-facts {
  display: grid; grid-template-columns: 9rem minmax(0, 1fr);
  gap: 9px 12px; margin: 13px 0 0; font-size: var(--fs-sm);
}
.peek-facts dt { color: var(--faint); font-size: var(--fs-xs); padding-top: 1px; }
.peek-facts dd { margin: 0; }

.add-row:hover { border-color: var(--accent); color: var(--accent); background: var(--surface); }
.add-row span { font-size: var(--fs-md); line-height: 1; }

.set-people { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: var(--fs-sm); }
.set-person { white-space: nowrap; }

/* The Save button appears only while the field is being edited. With
   JavaScript the field submits itself on Enter or on leaving it and this is
   never seen; without, focusing the field brings it back, so the page keeps
   working either way. The actions column stays reserved at its full width, so
   nothing moves when the button comes and goes. */
/* Two classes, not one: `.btn` sets its own display two hundred lines further
   down, and at equal specificity the later rule wins -- so a bare `.btn-save`
   here loses to it and the button stays visible. */
.set-actions .btn-save { display: none; }
.set-row:focus-within .set-actions .btn-save { display: inline-flex; }

/* A form that sits inside a sentence rather than owning a line. */
.inline-form { display: inline; }
.linky {
  background: none; border: 0; padding: 0 1px 0 4px; cursor: pointer;
  color: var(--faint); font-size: var(--fs-sm); line-height: 1;
}
.linky:hover { color: var(--danger); }

/* `.row` wraps by design, which is right for a checkbox and its sentence and
   wrong for a field with a button beside it -- the button drops to its own
   line and doubles the height of the thing. */
.row.nowrap { flex-wrap: nowrap; }

/* A label that sits beside its field rather than above it. */
.sr-inline { margin: 0; flex: none; align-self: center; }

/* One pair of rules per step. Written out because CSS cannot count and the
   list is a fixed five in stats.onboarding_steps() -- add a sixth there and it
   needs a sixth pair here, which is the trade for needing no JavaScript. */
.road-pick:nth-of-type(1):checked ~ .road-panels .road-panel:nth-child(1),
.road-pick:nth-of-type(2):checked ~ .road-panels .road-panel:nth-child(2),
.road-pick:nth-of-type(3):checked ~ .road-panels .road-panel:nth-child(3),
.road-pick:nth-of-type(4):checked ~ .road-panels .road-panel:nth-child(4),
.road-pick:nth-of-type(5):checked ~ .road-panels .road-panel:nth-child(5) {
  display: block;
}
.road-pick:nth-of-type(1):checked ~ .page-head .stepper-step:nth-child(1) .stepper-dot,
.road-pick:nth-of-type(2):checked ~ .page-head .stepper-step:nth-child(2) .stepper-dot,
.road-pick:nth-of-type(3):checked ~ .page-head .stepper-step:nth-child(3) .stepper-dot,
.road-pick:nth-of-type(4):checked ~ .page-head .stepper-step:nth-child(4) .stepper-dot,
.road-pick:nth-of-type(5):checked ~ .page-head .stepper-step:nth-child(5) .stepper-dot {
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.road-pick:nth-of-type(1):checked ~ .page-head .stepper-step:nth-child(1).later .stepper-dot,
.road-pick:nth-of-type(2):checked ~ .page-head .stepper-step:nth-child(2).later .stepper-dot,
.road-pick:nth-of-type(3):checked ~ .page-head .stepper-step:nth-child(3).later .stepper-dot,
.road-pick:nth-of-type(4):checked ~ .page-head .stepper-step:nth-child(4).later .stepper-dot,
.road-pick:nth-of-type(5):checked ~ .page-head .stepper-step:nth-child(5).later .stepper-dot {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.road-pick:nth-of-type(1):checked ~ .page-head .stepper-step:nth-child(1) .stepper-cap,
.road-pick:nth-of-type(2):checked ~ .page-head .stepper-step:nth-child(2) .stepper-cap,
.road-pick:nth-of-type(3):checked ~ .page-head .stepper-step:nth-child(3) .stepper-cap,
.road-pick:nth-of-type(4):checked ~ .page-head .stepper-step:nth-child(4) .stepper-cap,
.road-pick:nth-of-type(5):checked ~ .page-head .stepper-step:nth-child(5) .stepper-cap {
  color: var(--text);
}
/* The current step, whether or not it is the one being read. */
.stepper-step.now .stepper-dot { border-color: var(--accent); color: var(--accent); }

.stepper-step label:focus-within .stepper-dot {
  outline: 2px solid var(--accent); outline-offset: 3px;
}

/* Narrower than this and five captions cannot fit on the title row, and a
   truncating flex row renders them as "C…", "Thei…", "Google …" -- which is
   worse than not showing them. The circles keep their number, their colour and
   a title attribute, and the panel names the step when you click it.

   Measured against the *window*, and the sidebar takes 210px of it first. */
@media (max-width: 1400px) {
  .stepper-cap { display: none; }
  .stepper { flex: 0 0 auto; max-width: none; }
  .stepper-step { flex: 0 0 auto; }
  .stepper-step::after { min-width: 20px; flex: 0 0 20px; }
}
.check input[type="checkbox"] { flex: none; margin-top: 2px; accent-color: var(--accent); }
.check > span { flex: 1 1 0; min-width: 0; }
.check.note-warn input[type="checkbox"] { accent-color: var(--warn); }

/* ---------------------------------------------------------------- shell */
/* The rail's width is a custom property so one value drives the column, the
   grip's position and the breakpoints that have to account for it. Set on
   <html> by app.js from what you dragged it to last time; the default here is
   what everyone gets before they touch it, and what everyone gets if the
   script never runs. */
:root { --rail: 210px; }

.shell { display: flex; min-height: 100vh; }
.sidebar {
  width: var(--rail); flex: 0 0 var(--rail); background: var(--surface);
  border-right: 1px solid var(--line); display: flex; flex-direction: column;
  overflow: hidden;
}

/* The grip is its own element rather than a border, because a 1px target is
   not a target. It sits over the seam and widens on hover. */
.rail-grip {
  flex: 0 0 0; width: 0; position: relative; z-index: 5;
  border: 0; padding: 0; background: none; cursor: col-resize;
}
.rail-grip::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: -4px; width: 9px;
}
.rail-grip::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: -1px; width: 2px;
  background: transparent; transition: background .12s ease;
}
.rail-grip:hover::after, .rail-grip:focus-visible::after,
[data-rail-dragging] .rail-grip::after { background: var(--accent); }
.rail-grip:focus-visible { outline: none; }

/* While dragging, nothing else should be selectable or hover-y. */
[data-rail-dragging] { user-select: none; cursor: col-resize; }
[data-rail-dragging] * { pointer-events: none; }
[data-rail-dragging] .rail-grip { pointer-events: auto; }

/* Folded: the rail goes to nothing and the toggle in the topbar brings it
   back. Hidden rather than narrowed, because a 48px strip of truncated words
   is worse than no strip at all.

   The width is overridden on `.sidebar` rather than by redefining `--rail` on
   the root. Dragging writes `--rail` as an *inline* style on <html>, and an
   inline style beats a stylesheet rule on the same element -- so folding
   appeared to work (the attribute flipped) while the sidebar stayed exactly
   where it was. Styling a descendant sidesteps the fight. */
[data-rail="folded"] .sidebar {
  width: 0; flex-basis: 0; border-right: 0;
}
[data-rail="folded"] .rail-grip { display: none; }

.rail-toggle {
  flex: none; background: none; border: 0; cursor: pointer; padding: 5px;
  margin-right: 4px; margin-left: -4px; border-radius: 6px;
  color: var(--muted); line-height: 0;
}
.rail-toggle:hover { color: var(--text); background: var(--surface-2); }
.brand { display: flex; align-items: center; gap: 9px; padding: 14px; border-bottom: 1px solid var(--line-soft); }
.brand-mark {
  width: 28px; height: 28px; flex: 0 0 auto; display: block;
}
.brand-name { font-size: var(--fs-md); font-weight: 700; }
.brand-sub { font-size: var(--fs-xs); color: var(--muted); margin-top: 1px; }
.nav { padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; }
.nav a {
  display: flex; align-items: center; gap: 9px; padding: 7px 10px;
  border-radius: var(--radius-sm); color: var(--muted); font-weight: 500;
}
.nav a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.nav a.on { background: var(--accent-soft); color: var(--accent-strong); font-weight: 600; }
.nav .ico { width: 15px; flex: 0 0 auto; opacity: 0.85; text-align: center; }
.badge {
  font-size: var(--fs-xs); font-weight: 700; background: var(--warn);
  color: var(--on-warn); padding: 1px 7px; border-radius: 3px; margin-left: auto;
}
.side-foot {
  margin: 12px; padding: 11px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--line-soft);
}
.main { flex-grow: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  height: 48px; flex: 0 0 48px; display: flex; align-items: center; gap: 10px;
  padding: 0 18px; border-bottom: 1px solid var(--line); background: var(--surface);
  /* Never wrap into a second line and eat the content below it. */
  white-space: nowrap; overflow-x: auto; overflow-y: hidden;
}
.topbar::-webkit-scrollbar { height: 0; }
.content { padding: 18px; display: flex; flex-direction: column; gap: 14px; }
.page-head { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.page-title { font-size: 20px; font-weight: 700; letter-spacing: -0.02em; margin: 0; }
.page-sub { font-size: var(--fs-body); color: var(--muted); margin-top: 3px; max-width: 66ch; }

/* ------------------------------------------------------------ controls */
.btn {
  display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--muted);
  font-size: var(--fs-sm); font-weight: 600; font-family: inherit;
  white-space: nowrap; cursor: pointer;
}
.btn:hover { border-color: var(--faint); color: var(--text); text-decoration: none; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--on-accent); }
.btn-danger { color: var(--danger); border-color: var(--danger-soft); }
.btn-sm { padding: 3px 9px; font-size: var(--fs-xs); }
.btn:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.field {
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 8px 11px;
  font-size: var(--fs-md); color: var(--text); width: 100%;
  font-family: inherit;
}
.field:focus { border-color: var(--accent); outline: none; }
.field-label { font-size: var(--fs-xs); color: var(--faint); margin-bottom: 5px; display: block; }
.g2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* The constraint is the content area, not the window: the sidebar takes the
   rail's width before any of this gets a say, so the breakpoint has to account
   for it or a two-column layout stays two columns long after it stops fitting.
   940px is 720 plus the default rail; a dragged rail moves the real threshold,
   which is the price of a media query not being able to measure an element. */
@media (max-width: 940px) { .g2 { grid-template-columns: 1fr !important; } }

/* --------------------------------------------------------------- cards */
.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.card-head { display: flex; align-items: center; gap: 10px; padding: 11px 15px; border-bottom: 1px solid var(--line-soft); }
.card-title { font-size: var(--fs-md); font-weight: 700; }
.card-pad { padding: 14px 16px; }
.note { background: var(--surface-2); border-radius: var(--radius-sm); padding: 11px 13px; font-size: var(--fs-sm); color: var(--muted); line-height: 1.55; }
.note-accent { background: var(--accent-soft); color: var(--text); }
.note-warn { background: var(--warn-soft); color: var(--warn); }
.note-danger { background: var(--danger-soft); color: var(--danger); }
.note-ok { background: var(--ok-soft); color: var(--ok); }

.pill {
  font-size: 9px; font-weight: 700; padding: 2px 8px; border-radius: 3px;
  display: inline-block; white-space: nowrap; text-transform: uppercase;
  letter-spacing: 0.05em;
}
.pill-ok { color: var(--ok); background: var(--ok-soft); }
.pill-warn { color: var(--warn); background: var(--warn-soft); }
.pill-bad { color: var(--danger); background: var(--danger-soft); }
.pill-neutral { color: var(--muted); background: var(--surface-3); }

/* -------------------------------------------------------------- tables */
.table { display: flex; flex-direction: column; }
.tr { display: grid; align-items: center; padding: 9px 15px; gap: 12px; border-bottom: 1px solid var(--line-soft); }
.tr:last-child { border-bottom: 0; }
.tr.head { border-bottom: 1px solid var(--line); }
.tr.head > * { font-size: 9.5px; font-weight: 700; color: var(--faint); letter-spacing: 0.06em; text-transform: uppercase; }
.tr:not(.head):hover { background: var(--surface-2); }
.empty { padding: 40px 20px; text-align: center; color: var(--muted); }

/* -------------------------------------------------- the information icon */
/* The plain name is the label; the technical name is the tooltip. Our own
   staff benefit from this too -- a new hire reads "who's on the team" and
   understands the system faster than they would from a source constant. */
.info {
  display: inline-flex; width: 14px; height: 14px; border-radius: 50%;
  border: 1px solid var(--faint); color: var(--faint); font-size: 9px;
  align-items: center; justify-content: center; font-weight: 700;
  font-style: normal; vertical-align: 1px; cursor: help; position: relative;
}
.info:hover { border-color: var(--accent); color: var(--accent); }
.info .tip {
  display: none; position: absolute; top: 20px; left: -8px; width: 268px;
  background: var(--surface-3); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 9px 11px; font-size: var(--fs-xs);
  color: var(--muted); z-index: 20; box-shadow: var(--shadow-pop);
  line-height: 1.55; text-align: left; font-weight: 400; letter-spacing: 0;
  text-transform: none;
}
.info:hover .tip, .info:focus .tip { display: block; }
.info .tip code { font-family: var(--font-mono); color: var(--text); }

/* ----------------------------------------------------------- sign in */
.auth-wrap { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 24px; }
.auth-card { width: 392px; max-width: 100%; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 28px; }
.auth-title { font-size: 19px; font-weight: 700; letter-spacing: -0.02em; margin: 0 0 6px; }
.auth-sub { color: var(--muted); margin: 0 0 18px; font-size: var(--fs-md); }
.code-input {
  font-family: var(--font-mono); font-size: 26px; letter-spacing: 0.34em;
  text-align: center; padding: 12px;
}
.err { background: var(--danger-soft); color: var(--danger); border-radius: var(--radius-sm); padding: 10px 12px; font-size: var(--fs-sm); margin-bottom: 14px; }
.ok-msg { background: var(--ok-soft); color: var(--ok); border-radius: var(--radius-sm); padding: 10px 12px; font-size: var(--fs-sm); margin-bottom: 14px; }

/* ------------------------------------------------- the customer's view
   A Slack thread, rendered from the same Block Kit we sent. Light on
   purpose: the customer's world is not our console, and making it look
   like ours would hide exactly the thing we are here to judge. */
.sl-frame {
  background: #ffffff; color: #1d1c1d; border-radius: 8px;
  /* Deliberately Slack's palette, not ours, in both schemes: this is a
     rendering of their surface. The border is what keeps it reading as
     something embedded once our own background is also light. */
  border: 1px solid var(--line); box-shadow: var(--shadow-card);
  font-family: "Lato", -apple-system, "Segoe UI", sans-serif;
  font-size: 15px; line-height: 1.46; overflow: hidden;
}
.sl-head {
  border-bottom: 1px solid #e8e8e8; padding: 11px 16px;
  display: flex; align-items: center; gap: 8px; font-weight: 900;
}
.sl-app {
  background: #ddd; color: #4a4a4a; font-size: 10px; font-weight: 700;
  padding: 1px 4px; border-radius: 2px; text-transform: uppercase;
}
.sl-body { padding: 12px 16px 18px; }
.sl-msg { display: flex; gap: 9px; padding: 8px 0 4px; }
.sl-av {
  width: 34px; height: 34px; border-radius: 7px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; font-weight: 900;
}
.sl-av-bot { background: #4a154b; color: #fff; }
.sl-av-them { background: #e8f5e9; color: #2e7d32; }
.sl-mb { min-width: 0; flex-grow: 1; }
.sl-meta { display: flex; align-items: baseline; gap: 7px; margin-bottom: 2px; }
.sl-name { font-weight: 900; }
.sl-time { color: #868686; font-size: 12px; }
.sl-only { color: #868686; font-size: 12px; font-style: italic; }
.sl-section { margin-bottom: 8px; }
.sl-section:last-child { margin-bottom: 0; }
.sl-header { font-size: 18px; font-weight: 900; margin-bottom: 8px; }
.sl-context { color: #616061; font-size: 13px; margin-top: 8px; }
.sl-divider { height: 1px; background: #e8e8e8; margin: 12px 0; }
.sl-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 18px; margin: 8px 0; }
.sl-field { font-size: 13.5px; }
.sl-code {
  font-family: var(--font-mono); font-size: 13px; background: #f6f6f6;
  border: 1px solid #e8e8e8; border-radius: 3px; padding: 1px 4px; color: #e01e5a;
}
.sl-pre {
  font-family: var(--font-mono); font-size: 12.5px; background: #f6f6f6;
  border: 1px solid #e8e8e8; border-radius: 4px; padding: 9px 11px;
  color: #1d1c1d; white-space: pre-wrap; word-break: break-all; margin: 8px 0;
}
.sl-mention { color: #1264a3; background: #e8f5fa; border-radius: 3px; padding: 0 2px; }
.sl-actions { display: flex; gap: 8px; margin-top: 11px; flex-wrap: wrap; }
.sl-btn {
  border: 1px solid #ddd; background: #fff; border-radius: 4px;
  padding: 7px 13px; font-size: 14px; font-weight: 700; color: #1d1c1d;
  font-family: inherit; cursor: pointer;
}
.sl-btn:hover { background: #f8f8f8; }
.sl-btn-primary { background: #007a5a; border-color: #007a5a; color: #fff; }
.sl-btn-primary:hover { background: #148567; }
.sl-btn-danger { background: #e01e5a; border-color: #e01e5a; color: #fff; }
.sl-btn-dead { opacity: 0.5; cursor: default; }
.sl-btn-dead:hover { background: #fff; }
.sl-frame a { color: #1264a3; }

/* Wide tables scroll inside their own card rather than overlapping.
   Found by looking at the console in a narrow window: fixed grid columns
   summing past the available width silently overlap instead of wrapping,
   which reads as corrupted data rather than as a layout problem. */
.table-wrap { overflow-x: auto; }
/* Two things had to be right here, and each failed silently on its own:
   a *definite* width, because inside a shrink-to-fit scroll container a
   `minmax(0,1fr)` column resolves to 0px and its contents land on top of the
   next column; and enough of it, because `box-sizing: border-box` means the
   row's 30px of padding comes out of this number before the columns are laid
   out. Too small and the flexible column goes negative, which looks like
   corrupted data rather than a layout problem.
   `max()` keeps it fluid on a wide screen and scrolls on a narrow one. */
.table-wrap .tr { width: max(100%, var(--table-min, 860px)); }


/* ---------------------------------------------------------- theme control */
.theme-pick {
  display: flex; gap: 2px; margin-top: 9px;
  background: var(--surface-3); border-radius: var(--radius-sm); padding: 2px;
}
.theme-pick button {
  flex: 1; border: 0; background: transparent; color: var(--muted);
  font-family: inherit; font-size: var(--fs-xs); font-weight: 600;
  padding: 4px 2px; border-radius: 3px; cursor: pointer;
}
.theme-pick button:hover { color: var(--text); }
.theme-pick button[aria-pressed="true"] {
  background: var(--surface); color: var(--text); box-shadow: var(--shadow-card);
}

/* ------------------------------------------------------------- dialogs
   For jobs done occasionally. A form that is always on screen is clutter
   for the other twenty-nine days of the month. */
dialog.modal {
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  padding: 0;
  width: min(560px, calc(100vw - 32px));
  box-shadow: var(--shadow-pop);
}
dialog.modal::backdrop { background: rgba(8, 8, 12, 0.55); }
dialog.modal .modal-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 15px 18px; border-bottom: 1px solid var(--line-soft);
}
dialog.modal .modal-title { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
dialog.modal .modal-sub { font-size: var(--fs-sm); color: var(--muted); margin-top: 3px; }
dialog.modal .modal-body { padding: 16px 18px; display: flex; flex-direction: column; gap: 13px; }
dialog.modal .modal-foot {
  display: flex; align-items: center; gap: 9px;
  padding: 13px 18px; border-top: 1px solid var(--line-soft); background: var(--surface-2);
}
dialog.modal .modal-x {
  border: 0; background: transparent; color: var(--faint);
  font-size: 18px; line-height: 1; cursor: pointer; padding: 2px 4px;
}
dialog.modal .modal-x:hover { color: var(--text); }

/* The "+" that opens one. Square, quiet, and obvious. */
.btn-add {
  width: 28px; height: 28px; padding: 0;
  justify-content: center; font-size: 17px; line-height: 1; font-weight: 400;
}

/* -------------------------------------------------------- the user menu
   Identity at the top, everything you might do about it folded away. A
   column of controls you touch twice a month is not information. */
.usermenu { border-bottom: 1px solid var(--line-soft); }
.usermenu > summary {
  display: flex; align-items: center; gap: 9px; padding: 13px 14px;
  cursor: pointer; list-style: none; user-select: none;
}
.usermenu > summary::-webkit-details-marker { display: none; }
.usermenu > summary:hover { background: var(--surface-2); }
.usermenu .who { min-width: 0; flex-grow: 1; }
.usermenu .who-name {
  display: block; font-size: var(--fs-md); font-weight: 700;
  letter-spacing: -0.015em; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
.usermenu .who-role { display: block; font-size: var(--fs-xs); color: var(--muted); margin-top: 1px; }
.usermenu .chev {
  color: var(--muted); flex: 0 0 auto; display: block;
  transition: transform 0.12s ease;
}
.usermenu > summary:hover .chev { color: var(--text); }
.usermenu[open] .chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .usermenu .chev { transition: none; } }
.usermenu-panel {
  padding: 4px 12px 12px; display: flex; flex-direction: column; gap: 9px;
  background: var(--surface-2);
}
.usermenu-email { font-size: var(--fs-xs); color: var(--faint); word-break: break-all; }
.usermenu-actions { display: flex; flex-direction: column; gap: 5px; }
.usermenu-actions .btn { width: 100%; justify-content: center; }
.usermenu-quiet { border-color: transparent; color: var(--faint); }
.usermenu-quiet:hover { border-color: var(--line); }

/* --------------------------------------------------- looking as someone
   Deliberately loud. Forgetting you are seeing someone else's view is how
   you file a bug about data that is simply not theirs. */
.viewing-as {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: 12px;
  padding: 9px 16px; font-size: var(--fs-sm);
  background: var(--warn-soft); color: var(--warn);
  border-bottom: 1px solid var(--warn);
}
.viewing-as b { color: var(--text); }
.viewing-as .btn { background: var(--surface); }

/* ------------------------------------------------------- vendor marks
   A fetched favicon, or initials. No placeholder image: a vendor we have
   no icon for should look like one, not like a broken fetch. */
.vmark {
  border-radius: 5px; flex: 0 0 auto; display: inline-block;
  object-fit: contain; vertical-align: middle;
  /* Always white, in both themes, with a hairline edge. A favicon is drawn to
     sit on a browser's own chrome, and plenty of them are white on transparent
     -- on a light surface those simply disappear. This is the one place a
     literal colour is right: the icons assume it. */
  background: #ffffff;
  box-shadow: inset 0 0 0 1px var(--line);
  padding: 1px;
}
.vmark-text {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-weight: 700;
  background: var(--surface-3); color: var(--muted);
}
.icon-state { font-size: var(--fs-xs); }
.icon-state.ok { color: var(--ok); }
.icon-state.pending { color: var(--warn); }
.icon-state.failed { color: var(--danger); }

/* A name that opens something should look like it does. */
.link-name { font-weight: 600; color: var(--accent-strong); }
.link-name:hover { text-decoration: underline; }

/* Breadcrumbs. The trail is only useful if you can walk back up it. */
.crumb { color: var(--muted); font-size: var(--fs-xs); font-family: var(--font-mono); }
a.crumb:hover { color: var(--accent-strong); text-decoration: underline; }

/* One customer's tables, as tabs. Links, not a script -- see the macro. */
.ctabs {
  display: flex; gap: 2px; flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin: 20px 0 0; padding: 0 2px;
}
.ctab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 13px; color: var(--muted); text-decoration: none;
  border-bottom: 2px solid transparent; font-size: var(--fs-md);
}
.ctab:hover { color: var(--text); }
.ctab.on { color: var(--text); font-weight: 700; border-bottom-color: var(--accent); }
.ctab-n {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  background: var(--surface-3); color: var(--muted);
  border-radius: 10px; padding: 1px 6px;
}
.ctab.on .ctab-n { background: var(--accent-soft); color: var(--accent-strong); }
/* The one count that means "waiting for somebody" rather than "how many". */
.ctab-n.warn { background: var(--warn-soft); color: var(--warn); }

/* Noticed, in the corner. The word says which scope you are looking at: the
   same rows appear in "Needs you" across every customer, and containment you
   can see beats containment you discover by clicking. */
.btn.noticed { position: relative; }
.btn.noticed .scope {
  color: var(--faint); font-weight: 400;
  font-size: var(--fs-xs); letter-spacing: .04em; margin-left: 3px;
}
.btn.noticed.has-unseen { border-color: var(--danger); }
.dot-count {
  position: absolute; top: -7px; right: -7px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 9px; background: var(--danger); color: #fff;
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  display: grid; place-items: center; border: 2px solid var(--surface);
}

/* The filter bar. Links and one small form -- the URL is the state, so a
   filtered table can be sent to somebody and Back undoes a filter. */
.filterbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 14px; border-bottom: 1px solid var(--line-soft);
}
.field-search { min-width: 200px; padding: 5px 9px; font-size: var(--fs-sm); }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-sm); padding: 3px 5px 3px 9px; border-radius: 20px;
  background: var(--accent-soft); color: var(--accent-strong);
}
.chip b { font-weight: 700; }
.chip-x {
  text-decoration: none; color: inherit; opacity: .7;
  padding: 0 4px; border-radius: 50%; line-height: 1;
}
.chip-x:hover { opacity: 1; background: var(--surface-3); }

/* "+ Filter" as a details/summary, so the menu needs no script. */
.addfilter { position: relative; }
.addfilter > summary { list-style: none; cursor: pointer; display: inline-block; }
.addfilter > summary::-webkit-details-marker { display: none; }
.addfilter-menu {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 20;
  min-width: 190px; padding: 5px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-pop);
  display: grid; gap: 1px;
}
.addfilter-menu a {
  display: block; padding: 6px 9px; border-radius: var(--radius-sm);
  color: var(--text); text-decoration: none; font-size: var(--fs-sm);
}
.addfilter-menu a:hover { background: var(--surface-2); }

/* A sortable heading is a link, and says which way it is pointing. */
.sort-head {
  color: inherit; text-decoration: none; display: inline-flex; gap: 3px;
  align-items: center;
}
.sort-head:hover { color: var(--text); }
.sort-head.on { color: var(--text); }
.sort-head.right { justify-content: flex-end; width: 100%; }
.sort-arrow { font-size: 9px; opacity: .8; }

/* A fortnight of a feed. Fourteen numbers do not need a chart library. */
.spark { display: block; overflow: visible; }
.spark-cell { display: flex; align-items: center; justify-content: flex-end; }

/* A number on the dashboard that opens the list behind it. It has to look
   like something you can press, or it is a number somebody reads and then
   goes and finds by hand. */
.stat-link {
  display: block; text-decoration: none; color: inherit;
  border-radius: var(--radius-sm); padding: 7px 9px; margin: -7px -9px;
  border: 1px solid transparent;
}
.stat-link:hover { background: var(--accent-soft); border-color: var(--accent); }
.stat-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.stat-link .stat-go {
  display: block; font-size: var(--fs-xs); color: var(--faint); margin-top: 1px;
  opacity: 0; transition: opacity .12s;
}
.stat-link:hover .stat-go, .stat-link:focus-visible .stat-go { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .stat-link .stat-go { transition: none; } }

/* A model's guess at what an app is. Marked, and marked differently from a
   catalogue match: that one means unrelated customers independently agreed,
   this one means a model read a name. Both are proposals somebody presses yes
   to, and only one of them is evidence. */
.guess-mark {
  display: inline-grid; place-items: center; flex: none;
  width: 18px; height: 18px; border-radius: 4px;
  background: var(--surface-3); color: var(--muted);
  border: 1px dashed var(--line);
  font-size: 10px; font-weight: 700; font-family: var(--font-mono);
}
