/* The operator app. Dense, factual, phone-first -- an operator checks the
   system from wherever they are, so "is it alive" must answer at 390px.

   2026-08-15, on the owner's verdict that this screen was ugly: re-skinned
   onto the SAME design system as the customer app rather than restyled by
   hand. Two products that share a name and share nothing else read as one
   finished thing and one internal tool, and the operator app is the one the
   owner spends their day in.

   What that means concretely: the type and space scales below are the same
   steps the customer app snaps to, the accent is Mithqal's gold, and the
   brand wears Playfair like the marque does. Every class name in this file is
   unchanged -- this is a re-skin, not a rewrite, so nothing in main.js had to
   move to get it. */
/* The palette and the scales live in tokens.css, loaded before this file.
   They were duplicated here, and the two copies drifted: this app's ground was
   blue-navy #080B14 while the customer app had been re-skinned warm to
   #0A0908, and U4 in the readiness plan recorded "both apps share one design
   system" the whole time because nothing checked it.

   Anything below may USE a token. Nothing below may define one. */
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--ink-1);
  font-family: "Inter Tight", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* A faint gold wash from the top edge. The single cheapest thing that makes
     a dark screen read as designed rather than as a default background. */
  background-image: radial-gradient(1100px 420px at 50% -160px,
                                    rgba(201, 162, 39, 0.07), transparent 70%);
  background-attachment: fixed;
}
:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px;
                 border-radius: var(--radius-sm); }

.admin-bar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  background: rgba(14, 19, 32, 0.86);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
.admin-brand {
  font-family: "Playfair Display", Georgia, serif;
  font-size: var(--text-lg); letter-spacing: 0.01em; color: var(--ink-1);
}
.admin-brand b {
  font-family: "Inter Tight", system-ui, sans-serif;
  color: var(--gold-2); font-weight: 600; font-size: var(--text-2xs);
  text-transform: uppercase; letter-spacing: 0.14em;
  vertical-align: 0.32em; margin-inline-start: var(--space-2);
}
.admin-nav { display: flex; gap: var(--space-1); flex-wrap: wrap; }
.admin-nav a {
  padding: var(--space-2) var(--space-3); border-radius: 999px;
  color: var(--ink-2); text-decoration: none; font-size: var(--text-sm);
  min-height: 36px; display: inline-flex; align-items: center;
  border: 1px solid transparent; transition: color 120ms var(--ease),
                                             background 120ms var(--ease);
}
.admin-nav a:hover { color: var(--ink-1); background: rgba(125, 155, 205, 0.08); }
.admin-nav a.is-active {
  background: rgba(201, 162, 39, 0.12); color: var(--gold-2);
  border-color: rgba(201, 162, 39, 0.34);
}
.admin-refreshed { margin-left: auto; color: var(--ink-3);
                   font-size: var(--text-2xs); font-variant-numeric: tabular-nums; }
.admin-exit {
  color: var(--ink-3); font-size: var(--text-xs); text-decoration: none;
  min-height: 36px; display: inline-flex; align-items: center;
  padding-inline: var(--space-2); border-radius: var(--radius-sm);
  border: 1px solid var(--line);
}
.admin-exit:hover { color: var(--ink-1); border-color: var(--line-strong); }
.admin-body { padding: var(--space-5) var(--space-4); max-width: 1400px;
              margin: 0 auto; }
/* A refusal is a screen the operator has to act on, not a line of red text.
   It carries the reason and the way out, so it is built like a card. */
.admin-error {
  color: var(--bad); background: var(--s1);
  border: 1px solid var(--bad); border-radius: 10px;
  padding: 14px; margin: 0 0 12px;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.admin-error p { margin: 0; }
.admin-signin {
  margin-left: auto; padding: 8px 16px; border-radius: 999px;
  background: var(--bad); color: var(--ink-1); border: 0;
  font: inherit; font-size: 0.86rem; text-decoration: none; cursor: pointer;
  min-height: 36px; display: inline-flex; align-items: center;
}
/* On a phone the row wraps; the action must not end up half off the edge. */
@media (max-width: 460px) {
  .admin-signin { margin-left: 0; }
}

/* align-items:start, and it is the single highest-value character in this
   file. The default `stretch` equalised every card in a row to its tallest
   sibling, which measured 1,318px of empty card interior on one screen -- an
   "AI" card 334x469px containing the word "healthy". Cards are now the height
   of what is in them. */
.grid { display: grid; gap: var(--space-4); align-items: start;
        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.stack { display: flex; flex-direction: column; gap: var(--space-4); }
.card {
  background: linear-gradient(180deg, var(--s2), var(--s1));
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-5); box-shadow: var(--shadow-sm);
}
.card h2 {
  margin: 0 0 var(--space-4); padding-block-end: var(--space-3);
  border-block-end: 1px solid var(--line);
  font-size: var(--text-2xs); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--gold-2);
}
.card h2 .muted { letter-spacing: 0; text-transform: none; }
.metric { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
          font-size: 1.15rem; font-weight: 600; margin-bottom: 10px;
          /* The live price re-flowed its digits on every tick because the one
             element that must look machined was the only one not tabular. */
          font-variant-numeric: tabular-nums; }
.muted { color: var(--ink-3); font-size: 0.8rem; font-weight: 400; }

.light { display: inline-flex; align-items: center; gap: 8px; }
.light::before { content: ""; width: 9px; height: 9px; border-radius: 50%; }
.light-ok::before { background: var(--ok); }
.light-warn::before { background: var(--warn); }
.light-bad::before { background: var(--bad); }
/* Colour marks DEVIATION, not category.
   Six green words -- ALLOWED, chain intact, 1 subscriber, ok, healthy, the
   price -- gave the eye six equal anchors and therefore none, while the one
   thing actually wrong was a bullet in a banner. Nominal state is written in
   the ordinary ink and keeps its green DOT; only warn and bad colour their
   type, because only they are asking for something. */
.light-ok { color: var(--ink-1); }
.light-warn { color: var(--warn); }
.light-bad { color: var(--bad); }

dl { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; margin: 0;
     font-size: 0.84rem; font-variant-numeric: tabular-nums; }
dl > div { display: contents; }
dt { color: var(--ink-3); }
dd { margin: 0; text-align: right; }

/* Tables scroll inside their own container -- the page never scrolls sideways. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; font-size: 0.8rem;
        font-variant-numeric: tabular-nums; white-space: nowrap; }
th { text-align: left; color: var(--ink-3); font-weight: 500; padding: 6px 10px 6px 0;
     border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--s1); }
td { padding: 6px 10px 6px 0; border-bottom: 1px solid var(--line); }
tr.is-excluded td { opacity: 0.5; text-decoration: line-through; }
.pos { color: var(--ok); }
.neg { color: var(--bad); }

/* The result column. Won and lost are the only two things allowed to carry
   colour in the record tables, so the eye lands on the verdict and not on the
   direction. "No result yet" is deliberately quiet: it is not a warning, it is
   an answer that has not arrived. */
.res-won { color: var(--ok); font-weight: 600; }
.res-lost { color: var(--bad); font-weight: 600; }
.res-pending { color: var(--ink-3); font-style: italic; }
.res-flat { color: var(--ink-2); }
/* Direction is a category. It used to borrow the win/loss palette. */
.side-cell { color: var(--ink-2); }
.warn { color: var(--flag, #d9a441); }

@media (max-width: 560px) {
  .admin-bar { gap: 8px; padding: 8px 10px; }
  .admin-refreshed { display: none; }
  /* Six destinations do not fit across a 390px phone. The nav takes its own
     row and scrolls inside itself, so the PAGE never scrolls sideways -- a
     page that shifts under the thumb is how an operator mis-taps a switch.
     The exit link keeps the brand's row. */
  .admin-brand { order: 1; }
  .admin-exit { order: 2; margin-left: auto; }
  .admin-nav { order: 3; flex: 1 0 100%; overflow-x: auto;
               scrollbar-width: none; -webkit-overflow-scrolling: touch;
               /* The edge fades where more tabs hide -- a cut with no hint
                  read as "Audit is missing" on the owner's phone. */
               mask-image: linear-gradient(to right, black calc(100% - 28px), transparent);
               -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent); }
  .admin-nav::-webkit-scrollbar { display: none; }
  .admin-nav a { flex: 0 0 auto; }
  .admin-body { padding: 10px; }
  .grid { grid-template-columns: 1fr; }
}
/* No prefers-color-scheme flip. This app used to turn WHITE on a light-mode
   operating system while the customer app stayed on its warm ground, so the
   two never looked like one product on the same machine. The customer app
   honours an explicit [data-theme] and nothing else; so does this one now. */

button, .admin-form button {
  min-height: 38px; padding: var(--space-2) var(--space-4);
  background: var(--s3); color: var(--ink-1);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  font: inherit; font-weight: 500; cursor: pointer;
  transition: border-color 120ms var(--ease), background 120ms var(--ease),
              transform 120ms var(--ease);
}
button:hover:not(:disabled) {
  border-color: var(--gold); background: rgba(201, 162, 39, 0.10);
  color: var(--gold-2);
}
button:active:not(:disabled) { transform: translateY(1px); }
button:disabled { opacity: 0.42; cursor: default; }
/* The one action a screen exists for. Every card has at most one. */
.card > button:last-of-type, .head-action {
  background: linear-gradient(180deg, var(--gold-2), var(--gold));
  border-color: transparent; color: #0A0A0C; font-weight: 600;
}
.card > button:last-of-type:hover:not(:disabled),
.head-action:hover:not(:disabled) {
  color: #0A0A0C; filter: brightness(1.08);
  background: linear-gradient(180deg, var(--gold-2), var(--gold));
}
.admin-form { display: flex; flex-direction: column; gap: var(--space-2);
              max-width: 460px; }
.admin-form input, .admin-form textarea, .form-grid input,
.form-grid select, .form-grid textarea {
  padding: var(--space-2) var(--space-3); background: var(--s0, #080B14);
  color: var(--ink-1); border: 1px solid var(--line);
  border-radius: var(--radius-sm); font: inherit; inline-size: 100%;
  transition: border-color 120ms var(--ease);
}
.admin-form input:focus, .form-grid input:focus, .form-grid select:focus,
.form-grid textarea:focus { border-color: var(--gold); outline: none; }
code { background: var(--s2); padding: 4px 8px; border-radius: 6px;
       font-family: ui-monospace, monospace; letter-spacing: 0.05em; }

/* --- service switches ---------------------------------------------------- */
.feature-list { list-style: none; margin: 12px 0 0; padding: 0;
                display: flex; flex-direction: column; gap: 14px; }
.feature-list > li { border-top: 1px solid var(--line); padding-top: 12px; }
.feature-list > li:first-child { border-top: 0; padding-top: 0; }
.feature-list > li.is-off { opacity: 0.72; }
.feature-list p { margin: 4px 0 0; font-size: 13px; }
.feature-head { display: flex; align-items: center; justify-content: space-between;
                gap: 12px; flex-wrap: wrap; }
.feature-reason { color: var(--ink-1); font-style: italic; }

/* Switching a service off hits every paying customer at once. The control
   carries that weight rather than looking like "refresh". */
.btn-danger { border-color: var(--bad); color: var(--bad); }
.btn-danger:hover:not(:disabled) { background: var(--bad); color: #fff;
                                   border-color: var(--bad); }

/* Three-state per-customer choice. A segmented control, because "default" is
   a real answer and a two-state switch cannot express it. */
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px;
       overflow: hidden; }
.seg button { border: 0; border-radius: 0; background: var(--s2); color: var(--ink-1);
              padding: 6px 12px; font-size: 13px; min-height: 36px; }
.seg button + button { border-left: 1px solid var(--line); }
.seg button.is-active { background: var(--ink-1); color: var(--bg); font-weight: 600; }

.row-actions { display: flex; gap: 8px; flex-wrap: wrap; }

@media (max-width: 560px) {
  .feature-head { align-items: flex-start; }
  .seg { width: 100%; }
  .seg button { flex: 1; }
}

/* A wide table on a 390px phone is a table you cannot read. Rows marked
   `.stacks` become cards below 720px: each cell keeps its column name, so no
   column is lost to a horizontal scroll the operator has to discover. */
@media (max-width: 720px) {
  .stacks, .stacks tbody, .stacks tr, .stacks td { display: block; width: 100%; }
  .stacks thead { display: none; }
  .stacks tr { border: 1px solid var(--line); border-radius: 10px;
               padding: 10px 12px; margin-bottom: 10px; background: var(--s2); }
  .stacks td { border: 0; padding: 3px 0; display: flex; gap: 10px;
               align-items: baseline; justify-content: space-between; }
  .stacks td::before { content: attr(data-label); color: var(--ink-3);
                       font-size: 12px; letter-spacing: 0.04em; }
  .stacks td.row-actions { justify-content: flex-start; padding-top: 10px; }
  .stacks td.row-actions::before { content: none; }
}

/* --- triage: the one line an operator opens this app for ------------------ */
.triage { padding: 16px; border-radius: 12px; margin-block-end: 12px;
          background: var(--s1); border: 1px solid var(--line);
          border-inline-start: 4px solid var(--ok); }
.triage.is-warn { border-inline-start-color: var(--warn); }
.triage.is-bad, .triage.is-halt { border-inline-start-color: var(--bad); }
.triage > b { font-size: 1.1rem; display: block; }
.triage.is-ok > b { color: var(--ok); }
.triage.is-warn > b { color: var(--warn); }
.triage.is-bad > b, .triage.is-halt > b { color: var(--bad); }
.triage ul { margin: 10px 0 0; padding-inline-start: 18px;
             display: flex; flex-direction: column; gap: 4px; }
.triage li { font-size: 0.84rem; color: var(--ink-2); }
.triage li.is-bad, .triage li.is-halt { color: var(--bad); }
.triage li.is-warn { color: var(--warn); }

/* --- external feeds, with a way to act on them ---------------------------- */
.feed-list { list-style: none; margin: 10px 0 0; padding: 0;
             display: flex; flex-direction: column; gap: 10px; }
.feed-list > li { display: flex; align-items: center; justify-content: space-between;
                  gap: 12px; flex-wrap: wrap; }
.feed-list span { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.feed-list small { color: var(--ink-3); font-size: 0.72rem; }
.feed-refresh { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.feed-refresh button { font-size: 0.74rem; padding: 4px 10px; min-height: 32px; }
.feed-refresh small { font-size: 0.7rem; }
.card h3 { margin: 16px 0 8px; font-size: 0.72rem; letter-spacing: 0.08em;
           text-transform: uppercase; color: var(--ink-3); }
.card h3:first-of-type { margin-block-start: 12px; }
/* What the engine saw when it published a call. */
.v-saw { color: var(--ink-2); font-size: 0.74rem; }

/* --- new-customer form + row states -------------------------------------- */
.card h2 .head-action {
  float: inline-end; font-size: 0.72rem; padding: 6px 12px;
}
.form-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px; margin-block: 12px;
}
.form-grid label {
  display: flex; flex-direction: column; gap: 6px;
  font-size: 0.75rem; color: var(--ink-3);
}
.form-grid input {
  font: inherit; padding: 9px 12px; border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--s2); color: var(--ink-1);
}
.is-disabled-row td { opacity: 0.55; }

/* The way out: a real end to the shift, not a session aging into a bounce. */
.admin-logout { background: none; border: 0; cursor: pointer; font: inherit; }
.admin-logout:hover { color: var(--down, #B4635A); }

/* Website screen: upload labels act as buttons; the license table is
   dense and phone-scrollable like everything else here. */
.admin-upload { display: inline-block; min-height: 36px; padding: 6px 12px;
  background: var(--s2); color: var(--ink-1);
  border: 1px solid var(--line); border-radius: 8px;
  font: inherit; cursor: pointer; margin-inline-end: 8px; }
.admin-upload:hover { border-color: var(--ink-3); }
/* The file input inside it is visually hidden but STILL FOCUSABLE. `hidden`
   -- display:none -- drops an element out of the tab order, so a keyboard
   operator could tab to "remove" on every landing slot and to "upload" on
   none: they could empty the public site and not put it back. */
.admin-upload .visually-hidden { position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0; clip-path: inset(50%);
  overflow: hidden; white-space: nowrap; }
.admin-upload:focus-within { outline: 2px solid var(--warn);
  outline-offset: 2px; border-color: var(--ink-3); }
.admin-table { width: 100%; border-collapse: collapse; font-size: 13px;
  display: block; overflow-x: auto; }
.admin-table th { text-align: left; color: var(--ink-3); font-weight: 600;
  padding: 8px 10px; border-bottom: 1px solid var(--line);
  white-space: nowrap; }
.admin-table td { padding: 8px 10px; border-bottom: 1px solid var(--line);
  white-space: nowrap; }
.admin-table tr:last-child td { border-bottom: 0; }

/* The absence test: the operator's own ceiling, stated plainly. Colour
   carries the verdict because "blocked" means a customer is sitting inside
   the failure right now. */
.card.absence.is-blocked { border-color: color-mix(in srgb, var(--bad, #C96F63) 60%, transparent); }
.card.absence.is-blocked .absence-headline { color: var(--bad, #C96F63); font-weight: 600; }
.card.absence.is-autonomous .absence-headline { color: var(--ok, #5E9E7A); }
.absence-headline { margin-block: 6px 12px; line-height: 1.6; }
.absence-list { list-style: none; margin: 0 0 12px; padding: 0; }
.absence-list li { display: flex; justify-content: space-between; gap: 12px;
  align-items: flex-start; padding-block: 8px;
  border-block-end: 1px solid var(--line, rgba(255,255,255,.08)); }
.absence-list li:last-child { border-block-end: 0; }
.absence-list li.is-blocking b::after { content: " · blocking";
  color: var(--bad, #C96F63); font-weight: 600; font-size: .78rem; }
.absence-list small { display: block; opacity: .7; font-size: .78rem;
  line-height: 1.55; margin-block-start: 2px; }
.absence-count { font-variant-numeric: tabular-nums; font-weight: 700;
  white-space: nowrap; }
.absence-count small { display: inline; opacity: .6; font-weight: 400; }

/* Severity, corrected. The kill switch stops publishing for every customer
   and used to render identically to "refresh", while eight routine service
   toggles were filled red -- the loudest controls were the reversible ones.
   Fill-red is now reserved for what a person cannot take back. */
.btn-critical { background: #5A1F1A; border: 1px solid #D2776C;
  color: #FFE9E5; font-weight: 600; }
.btn-critical:hover:not(:disabled) { background: #6E2620; }
.btn-ok { border-color: var(--ok, #5E9E7A); color: var(--ok, #5E9E7A); }
.btn-warn { border-color: var(--warn, #C99A3A); color: var(--warn, #C99A3A); }

/* The alarm colour measured 4.29:1 on the card it sits on -- below AA, on the
   one word the operator must read at 3am. */
:root { --bad: #D2776C; }
/* The light-theme status colours were here, and they are gone with the light
   theme itself. They existed only to survive an OS preference this app no
   longer honours; leaving them would be a block that can never match, which
   is the kind of rule somebody later "fixes" by reintroducing the bug. */

/* The business card. The money number leads, because it is the one an owner
   opens this app to see, and the renewal cliff sits under it. */
.biz-mrr { font-size: 1.9rem; font-variant-numeric: tabular-nums; }
.biz-expiring { margin-block-start: 12px; }
.biz-expiring > b { display: block; margin-block-end: 6px;
  color: var(--warn, #C99A3A); font-size: .82rem; }
.kv dd.warn { color: var(--warn, #C99A3A); font-weight: 600; }


/* Nothing in this app acknowledged being touched: zero of 227 elements
   carried a transition. That absence is most of why it read as a page rather
   than an instrument. */
.card button, .admin-nav a, .seg, label.upload {
  transition: background-color .12s ease, border-color .12s ease,
              color .12s ease, transform .06s ease;
}
.card button:active:not(:disabled) { transform: scale(.98); }

/* Every control reachable by a thumb. 45 targets on Customers alone measured
   36px, and `disable` sat 8px from `reset password` at that size. */
@media (pointer: coarse) {
  .card button, .admin-nav a, .seg, label.upload,
  .feed-refresh button { min-block-size: 44px; }
  .row-actions { gap: 12px; }
}

/* The triage names an emergency; it now also leads to it. */
.triage li a { color: inherit; text-decoration: none;
  border-block-end: 1px solid currentColor; }

/* The hero row. Two things, at the size their consequence deserves: the money
   the business makes, and the work a person is waiting on. Everything else on
   this screen is diagnostic and sits below them. */
.hero-row { display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.hero-money, .hero-waiting { display: flex; flex-direction: column; gap: 2px;
  background: var(--s1); border: 1px solid var(--line); border-radius: 10px;
  padding: 18px 20px; text-decoration: none; color: inherit; }
.hero-eyebrow { font-size: .68rem; font-weight: 650; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-3); }
.hero-num { font-size: 2.6rem; font-weight: 640; line-height: 1.05;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
  color: var(--money, #E9C46A); }
.hero-sub { font-size: .84rem; color: var(--ink-2); }
/* The waiting tile is the only red thing on a healthy screen, and it is a
   link -- the triage used to name an emergency and offer no route to it. */
.hero-waiting { border-color: color-mix(in srgb, var(--bad) 55%, transparent);
  background: color-mix(in srgb, var(--bad) 9%, var(--s1)); }
.hero-waiting .hero-num { color: var(--bad); }
.hero-waiting:hover { border-color: var(--bad); }
/* The money's own footnote: customers who can sign in and are on no plan.
   They are worth $0 in the number above it and receive one style of alerts,
   so it sits INSIDE the money tile -- the gap only means something next to
   the figure it contradicts -- and links to the screen that fixes it. */
.hero-gap { margin-top: 6px; font-size: .8rem; font-weight: 600;
  color: var(--warn, #E9C46A); text-decoration: none;
  border-bottom: 1px dotted currentColor; align-self: flex-start; }
.hero-gap:hover { color: var(--ink); }
@media (max-width: 480px) { .hero-num { font-size: 2.2rem; } }


/* Tokens that were USED and never DEFINED.
   --money coloured the largest number on the operator's home screen and
   --down its counterpart; both resolved to their inline fallbacks, so the
   palette had two members nobody could find or change. */
:root { --money: #E9C46A; --down: var(--bad); }

/* Which clock a table is on. Two tables printed times three hours apart --
   one local, one raw UTC -- and nothing on the page said so. */
.zone-note { font-weight: 400; font-size: 0.72rem; color: var(--ink-3);
             letter-spacing: 0; text-transform: none; margin-inline-start: 8px; }

/* A designed focus state. There was not one :focus rule in this stylesheet,
   so keyboard users got Chrome's default ring -- and .seg clipped it flat on
   three sides with overflow:hidden. */
.seg { overflow: visible; }
:where(button, a, input, textarea, select):focus-visible {
  outline: 2px solid var(--warn); outline-offset: 2px; border-radius: 6px;
}


/* A refusal is not a hint. Every status message in this console rendered as
   .muted -- the same class as the explanatory text beside it -- so a success
   and a failure looked identical and both looked like prose. */
.note-bad { margin: 0 0 12px; padding: 8px 12px; border-radius: 8px;
            font-size: 0.82rem; line-height: 1.5; color: var(--bad);
            background: color-mix(in srgb, var(--bad) 10%, transparent);
            border-inline-start: 3px solid var(--bad); }
.note-ok  { margin: 0 0 12px; padding: 8px 12px; border-radius: 8px;
            font-size: 0.82rem; line-height: 1.5; color: var(--ink-1);
            background: color-mix(in srgb, var(--ok) 10%, transparent);
            border-inline-start: 3px solid var(--ok); }

/* "Not reported" is not a reading, and must not be able to pass for one.
   No colour of its own (green would say healthy, red would say broken --
   both are claims about a measurement that never arrived), a dotted
   underline to invite the tooltip carrying the server's reason. */
.not-reported { color: var(--ink-3); font-style: italic; font-weight: 500;
  text-decoration: underline dotted; text-underline-offset: 3px;
  cursor: help; }

/* Finding one person in a list of hundreds. */
.customer-filter { display: flex; align-items: center; gap: 8px;
  margin-block: 4px 12px; }
.customer-filter input { flex: 1; max-width: 320px; min-height: 36px;
  padding: 6px 10px; background: var(--s2); color: var(--ink-1);
  border: 1px solid var(--line); border-radius: 8px; font: inherit; }

/* A panel that takes focus when it opens must show that it has it. */
.card:focus-visible { outline: 2px solid var(--warn); outline-offset: 2px; }


/* ==================== the two screens added 2026-08-15 ====================
   Rooms and Telegram. Both are configuration screens, and a configuration
   screen lives or dies on whether the operator understands a field before
   they fill it -- so the hint under every input is the load-bearing element
   here, not the input. It gets real contrast and real line-height rather than
   the grey whisper that "help text" usually becomes. */
.op-field { display: block; margin-block-end: var(--space-4); }
.op-field-label {
  display: block; margin-block-end: var(--space-2);
  font-size: var(--text-xs); font-weight: 600; color: var(--ink-1);
  letter-spacing: 0.02em;
}
.op-hint {
  display: block; margin-block-start: var(--space-2);
  color: var(--ink-2); font-size: var(--text-xs); line-height: 1.55;
  max-inline-size: 62ch;
}
.op-lede {
  margin: 0 0 var(--space-4); color: var(--ink-2);
  font-size: var(--text-sm); line-height: 1.6; max-inline-size: 68ch;
}
/* NOT a redefinition of .form-grid. An earlier version of this block set
   `gap: 0` on it and flattened it to one column, which silently wrecked the
   layout of every form that already used it -- including "New customer", the
   screen the whole business runs through. The grid above (auto-fit, 220px
   minimum, 12px gap) is correct for these fields too; .op-field only has to
   stop adding a second margin on top of the grid's own gap. */
.form-grid .op-field { margin-block-end: 0; }

/* The master switch. Deliberately not a toggle: starting and stopping a
   broadcast channel is a decision, and a decision gets a button with a verb
   on it and a sentence beside it saying what is true right now. */
.op-switch {
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  padding: var(--space-4); margin-block-end: var(--space-3);
  background: var(--s0, #080B14); border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.op-switch button { min-inline-size: 180px; justify-content: center; }

.op-list { list-style: none; margin: var(--space-3) 0 0; padding: 0;
           display: flex; flex-direction: column; gap: var(--space-2); }
.op-list > li {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  background: var(--s0, #080B14); border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.op-list > li.is-off { opacity: 0.55; }
.op-list > li.is-open { border-color: var(--gold); }
.op-list-main { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.op-list-main b { font-size: var(--text-sm); }
.op-list-main span { font-size: var(--text-xs); overflow-wrap: anywhere; }
.op-list-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.op-list-actions button { min-height: 34px; font-size: var(--text-xs); }
.note-ok { color: var(--ok); }
.note-bad { color: var(--bad); }

@media (max-width: 560px) {
  .op-list > li { flex-direction: column; align-items: stretch; }
  .op-list-actions button { flex: 1; }
  .op-switch button { inline-size: 100%; }
}

/* ============================================================ the chart
   The owner's first question about this app was "where is the chart".
   It sits directly under the money, because those are the two things this
   screen is opened to answer. */
.op-chart { padding-block-end: var(--space-4); }
.op-chart-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  margin: 0 0 var(--space-4); padding-block-end: var(--space-3);
  border-block-end: 1px solid var(--line);
}
.op-chart-head h2 { margin: 0; padding: 0; border: 0; }
.op-frames { display: flex; gap: 4px; }
.op-frames button {
  min-block-size: 30px; padding: 0 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: transparent;
  color: var(--ink-3); font-size: var(--text-2xs); font-weight: 600;
  letter-spacing: 0.04em; cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.op-frames button:hover { color: var(--ink-1); border-color: var(--line-strong); }
.op-frames button.is-active {
  color: var(--gold-ink); background: var(--gold); border-color: var(--gold);
}
.op-chart-box { inline-size: 100%; min-block-size: 320px; }
/* A chart with no data must not leave a 320px hole with nothing in it. */
.op-chart-box.is-empty { min-block-size: 0; }

/* ================================================== system health, folded
   Six diagnostic cards used to sit open above the business. The triage
   banner already names anything that needs a human, so the detail belongs
   one click away rather than permanently on screen. */
.op-system { margin-block: var(--space-4); }
.op-system > summary {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); cursor: pointer; list-style: none;
  padding: var(--space-4) var(--space-5);
  background: linear-gradient(180deg, var(--s2), var(--s1));
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  font-size: var(--text-2xs); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--gold-2);
  transition: border-color .15s ease;
}
.op-system > summary::-webkit-details-marker { display: none; }
.op-system > summary:hover { border-color: var(--line-strong); }
.op-system > summary small {
  letter-spacing: 0; text-transform: none; color: var(--ink-3);
  font-size: var(--text-xs);
}
/* The affordance: a chevron that turns, so the fold reads as openable
   rather than as a header somebody made clickable by accident. */
.op-system > summary::after {
  content: "⌄"; margin-inline-start: auto; padding-inline-start: var(--space-3);
  color: var(--ink-3); font-size: 1rem; line-height: 1;
  transition: transform .18s ease;
}
.op-system[open] > summary::after { transform: rotate(180deg); }
.op-system[open] > summary { border-end-start-radius: 0; border-end-end-radius: 0; }
.op-system[open] > .grid { margin-block-start: var(--space-4); }

@media (max-width: 560px) {
  .op-chart-head { align-items: flex-start; }
  .op-frames { inline-size: 100%; }
  .op-frames button { flex: 1; padding: 0 6px; }
}

/* ==================================================== the app's own numbers
   Not a price chart. The owner can see gold anywhere; what he cannot see
   anywhere else is whether his product is growing. */
.stats .op-chart-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  margin: 0 0 var(--space-4); padding-block-end: var(--space-3);
  border-block-end: 1px solid var(--line);
}
.stats .op-chart-head h2 { margin: 0; padding: 0; border: 0; }
.op-frames { display: flex; gap: 4px; }
.op-frames button {
  min-block-size: 30px; min-inline-size: 44px; padding: 0 10px;
  border-radius: var(--radius-sm); border: 1px solid var(--line);
  background: transparent; color: var(--ink-3);
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .04em;
  cursor: pointer;
}
.op-frames button:hover { color: var(--ink-1); border-color: var(--line-strong); }
.op-frames button.is-active {
  color: var(--gold-ink); background: var(--gold); border-color: var(--gold);
}

.stat-grid {
  display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.stat-tile {
  display: flex; flex-direction: column; gap: 6px;
  padding: var(--space-4); border: 1px solid var(--line);
  border-radius: var(--radius-md); background: var(--s2);
}
.stat-label {
  font-size: var(--text-2xs); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3);
}
.stat-total {
  font-family: var(--font-numeric, ui-monospace, monospace);
  font-size: 1.5rem; font-variant-numeric: tabular-nums; color: var(--ink-1);
  line-height: 1.1;
}
.stat-note { color: var(--ink-3); font-size: var(--text-2xs); line-height: 1.4; }
.stat-spark, .stat-bars { inline-size: 100%; display: block; overflow: visible; }

.stat-cumulative {
  display: flex; flex-direction: column; gap: 6px;
  margin-block-start: var(--space-5); padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--line);
}

@media (max-width: 560px) {
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
  .op-frames button { flex: 1; }
}

/* ════════════════════════════════ classes the operator app rendered unstyled

   The same sweep that found 33 unstyled classes in the customer app found
   five here. `test_every_operator_class_has_a_rule` fails the build on the
   next one. */

/* Every numeric cell in every operator table. Without tabular figures a
   column of counts does not line up, and a column that does not line up is
   a column nobody scans -- which is the entire job of these tables. */
.num {
  font-family: var(--font-numeric, ui-monospace, monospace);
  font-variant-numeric: tabular-nums;
  text-align: end;
  white-space: nowrap;
}
/* A metric reporting that nothing needs attention. Stated in the calm
   colour: "no one is waiting on a payment" is good news, and rendering it
   in the same ink as a problem trains the operator to skim past both. */
.metric.ok { color: var(--ok); }

/* The operator shell. It had no rule at all, so the whole app inherited
   whatever the body happened to be. */
.admin { min-block-size: 100dvh; background: var(--bg); color: var(--ink-1); }

/* A published call that already carries a verdict, versus one that does
   not. A hairline, not a fill: this marks a row, it does not rank it. */
tr.has-verdict { border-inline-start: 2px solid var(--ok); }
/* Money awaiting collection. The one card on the operator's first screen
   that is about an action they have not taken yet. */
.hero-money.has-waiting { border-inline-start: 2px solid var(--warn); }

/* A filter that has stopped discriminating. Named in the row rather than
   left to the reader to spot in a column of decimals -- the whole point of
   this table is that decay is easy to miss. */
tr.is-dead { border-inline-start: 2px solid var(--bad); }
tr.is-dead td { color: var(--bad); }
tr.is-weak { border-inline-start: 2px solid var(--warn); }

/* The panel's shadow judgements. Operator-only: these are not signals, and
   showing one to a customer would make it one. */
.panel-shadow { margin-block-start: 12px; }
.panel-shadow p { margin: 0 0 8px; }
