/* Operator console.
   ── What this is ──

   A machine console, and it should read like one. Dense, monospaced where the
   content is data, sharp-cornered, with a single cold accent doing all the
   signalling. Not a website: a panel somebody keeps open on a second monitor.

   ── Two themes ──

   Dark is the default and the one this was designed in. Light exists because
   a console is sometimes read next to a window in daylight, and squinting at
   a dark panel in the sun is its own kind of broken. Every colour is a token
   defined in both, so nothing is ever light text on a light ground.

   ── Shared numbers ──

   The neutrals and the semantic colours match the shop admin's dark mode and
   the marketplace's moderation screen. Three applications in three
   repositories cannot share a stylesheet, so they share a set of values --
   changing one without the others is what makes a platform look assembled. */

:root,
:root[data-theme="dark"] {
  --bg:        #0E1210;
  --panel:     #151B18;
  --panel-2:   #1C2420;
  --line:      #26302B;
  --line-soft: #1F2823;
  --ink:       #DCE6E0;
  --ink-dim:   #7E8F87;
  --accent:    #4FD1B0;
  --accent-hover: #6FE3C6;
  --accent-dim: rgba(79, 209, 176, .14);
  /* Ink on the accent, not white: the accent is lifted for legibility on a
     dark ground and white on top of it lands around 2:1. */
  --on-accent: #08110E;
  --ok:        #3FB984;
  --warn:      #E0A33E;
  --bad:       #E4635C;
  --rail-w:    216px;
  --radius:    8px;
  --ff:        'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono:      'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

:root[data-theme="light"] {
  --bg:        #F4F6F5;
  --panel:     #FFFFFF;
  --panel-2:   #EDF1EF;
  --line:      #D6DEDA;
  --line-soft: #E4EAE7;
  --ink:       #16201C;
  --ink-dim:   #5E6D66;
  /* Darkened for a light ground: the dark theme's accent is 1.6:1 on white. */
  --accent:    #0E8E72;
  --accent-hover: #0A7660;
  --accent-dim: rgba(14, 142, 114, .10);
  --on-accent: #FFFFFF;
  --ok:        #1B8A5C;
  --warn:      #9A6A10;
  --bad:       #B23A34;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ff);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }

/* Numbers are data. Anything that lines up in a column, anything an operator
   compares at a glance, is monospaced -- that is most of this application. */
code, kbd, samp, pre, .mono { font-family: var(--mono); }

/* ── The shell ────────────────────────────────────────────────
   A fixed rail and a scrolling main, the same shape as the shop admin,
   because there is one person operating both and the muscle memory should
   carry across. */
.shell { display: flex; min-height: 100vh; }

.rail {
  width: var(--rail-w);
  flex-shrink: 0;
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border-right: 1px solid var(--line);
  overflow-y: auto;
}
/* When the rail is taller than the window it scrolls; nothing in it is
   squeezed. The dashboard switch clips its corners with overflow: hidden,
   which lets a flex item shrink to nothing -- on a short screen, and in the
   phone drawer, it came out as a 2px line. */
.rail > * { flex-shrink: 0; }

/* ── The prompt, then the name ──
   The two lines sit beside the cursor rather than under it: a 216px rail has
   room to spare on that line and stacking them made the header twice as tall
   as it needed to be. */
.rail-brand {
  display: flex;
  align-items: center;
  /* The gap is measured to the caret block, not to the ">_", so it reads
     tighter than the number suggests. */
  gap: .85rem;
  padding: .95rem 1rem;
  border-bottom: 1px solid var(--line);
}
.rail-brand-text { min-width: 0; }
/* A prompt, because that is what this is. */
.rail-mark {
  display: inline-block;
  font-family: var(--mono);
  font-weight: 700;
  font-size: 1rem;
  color: var(--accent);
  letter-spacing: -.05em;
}
.rail-mark::after {
  content: '';
  display: inline-block;
  width: .5em; height: 1em;
  margin-left: .15em;
  background: var(--accent);
  vertical-align: -.15em;
  animation: rail-blink 1.2s steps(1) infinite;
}
@keyframes rail-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }
/* A cursor that never stops is a distraction on a screen somebody keeps open
   all day, and motion is a real accessibility cost for some people. */
@media (prefers-reduced-motion: reduce) {
  .rail-mark::after { animation: none; opacity: .5; }
}
.rail-name {
  display: block;
  font-size: .95rem; font-weight: 700; letter-spacing: -.01em; line-height: 1.1;
}
.rail-sub {
  display: block;
  font-family: var(--mono);
  font-size: .64rem; letter-spacing: .22em; text-transform: uppercase;
  color: var(--ink-dim);
}

.rail-nav { flex: 1; padding: .7rem 0; }
.rail-group {
  padding: .9rem 1rem .3rem;
  font-family: var(--mono);
  font-size: .6rem; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-dim);
}
.rail-link {
  display: flex; align-items: center; gap: .6rem;
  padding: .42rem 1rem;
  font-size: .86rem;
  color: var(--ink-dim);
  border-left: 2px solid transparent;
}
.rail-link:hover { color: var(--ink); background: var(--panel-2); }
/* The page you are on: marked by the rail edge and the accent, nothing that
   changes its width and nudges its neighbours. */
.rail-link.on {
  color: var(--ink);
  background: var(--accent-dim);
  border-left-color: var(--accent);
  font-weight: 600;
}
.rail-glyph {
  width: 1.1rem; text-align: center; flex-shrink: 0;
  font-family: var(--mono); font-size: .9rem; color: var(--accent);
}
.rail-link.on .rail-glyph { color: var(--accent); }

.rail-foot { padding: .8rem 1rem 1rem; border-top: 1px solid var(--line); }
.rail-who {
  display: flex; align-items: center; gap: .45rem;
  font-size: .76rem; color: var(--ink-dim);
  word-break: break-all;
}
.rail-who a { color: var(--ink); }
.rail-who a:hover { color: var(--accent); }
.rail-who-dot {
  width: 7px; height: 7px; border-radius: 999px; flex-shrink: 0;
  background: var(--ok);
}
/* Named nobody. Worth a glance every time, so it is coloured like a warning
   rather than sitting quietly. */
.rail-who.shared .rail-who-dot { background: var(--warn); }
.rail-who.shared { color: var(--warn); }
.rail-signout-form { margin: .6rem 0 0; }
/* A button now (sign-out is a POST), dressed as the quiet link it was. */
.rail-signout {
  display: block; margin: 0; padding: 0;
  background: none; border: 0; cursor: pointer;
  font: inherit; font-size: .76rem; color: var(--ink-dim); text-align: left;
}
.rail-signout:hover { color: var(--bad); }

/* ── Main ─────────────────────────────────────────────────── */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: .85rem 1.4rem;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  position: sticky; top: 0; z-index: 20;
}
.topbar-title { display: flex; align-items: baseline; gap: .7rem; flex-wrap: wrap; min-width: 0; }
.topbar h1 { font-size: 1rem; font-weight: 700; letter-spacing: -.01em; }
.topbar-sub { font-family: var(--mono); font-size: .74rem; color: var(--ink-dim); }
/* No wrapping and no stretching. Wrapped, the lone icon button on the second
   line grew to the width of the whole group and read as a mystery bar. */
.topbar-acts {
  margin-left: auto;
  display: flex; align-items: center; gap: .5rem;
  flex: 0 0 auto; flex-wrap: nowrap;
}
.topbar-acts > * { flex: 0 0 auto; }
/* A two-word label wrapped into a two-line button and made the bar taller
   than everything beside it. */
.topbar-acts .act,
.topbar-acts .act-btn,
.topbar-acts button { white-space: nowrap; }
/* The two icon controls are square. Sized by the glyph they hold, they came
   out different widths and the pair looked accidental. */
/* The square ones only. Applied to every .act it also squashed the page's own
   action link, so "Shop Manager" came out as "Shop Manag". */
.topbar-acts > .act-icon { width: 30px; padding-left: 0; padding-right: 0; }

/* ── All three on one baseline ──
   The <a> and the <button> disagreed. A button carries the browser's own font
   and line-height and, as a flex item with the default `align-self: stretch`
   removed, sat a couple of pixels lower than the link beside it. Pinning the
   height and centring the glyph puts them on the same line whatever they are
   made of. */
.topbar-acts > .act,
.topbar-acts > .act-btn,
.topbar-acts > button,
.topbar-acts form > button {
  height: 28px;
  line-height: 1;
  align-self: center;
  vertical-align: middle;
}
/* A glyph, not a word: its own metrics, so it centres on the box rather than
   on a text baseline that assumes descenders. */
#theme-toggle { font-size: .95rem; padding-bottom: 1px; }

.content { padding: 1.4rem; flex: 1; }

/* One button shape for everything in the top bar, whether it is a link, a
   button or a form's submit. The old bar had three and they did not line up. */
.topbar .act,
.topbar .act-btn,
.topbar button,
.act-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
  min-height: 28px; padding: .25rem .6rem;
  font: inherit; font-size: .78rem; font-weight: 500;
  color: var(--ink-dim);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
}
.topbar .act:hover,
.topbar .act-btn:hover,
.topbar button:hover,
.act-btn:hover { color: var(--accent); border-color: var(--accent); }
.topbar form { display: inline-flex; }

/* ── Narrowing the rail, and the drawer on a phone ─────────────
   The shop admin's two controls, so habits carry across all three
   dashboards. On a desktop a chevron narrows the rail to its glyphs and the
   choice is remembered; each glyph gets its word as a tooltip, and the words
   stay in the page for a screen reader. Below 861px the rail is a drawer.

   The phone used to get the rail as a strip of links that scrolled sideways,
   on the grounds that a drawer is a tap an operator does not need. With
   fifteen destinations the strip showed three and hid the rest off the edge,
   group names and all -- the drawer shows every one of them, grouped. */
.rail-collapse, .rail-close, .topbar .rail-menu, .rail-backdrop { display: none; }
.rail-collapse, .rail-close, .topbar .rail-menu {
  align-items: center; justify-content: center; flex: 0 0 auto;
  padding: 0; font: inherit; line-height: 1;
  color: var(--ink-dim); background: transparent;
  border: 1px solid transparent; border-radius: 6px; cursor: pointer;
}
.rail-collapse:hover, .rail-close:hover { color: var(--ink); background: var(--panel-2); border-color: var(--line); }
.rail-collapse:focus-visible, .rail-close:focus-visible, .topbar .rail-menu:focus-visible,
.rail-link:focus-visible, .rail-signout:focus-visible, .rail-switch a:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

@media (min-width: 861px) {
  .rail { transition: width .16s ease; }
  .rail-collapse { display: inline-flex; width: 28px; height: 28px; margin-left: auto; font-size: .78rem; }

  /* The width itself, not --rail-w: the theme blocks set that on
     :root[data-theme], which outranks a class on <html>. */
  html.rail-collapsed .rail { width: 60px; overflow-x: hidden; }
  html.rail-collapsed .rail-brand { flex-direction: column; gap: .55rem; padding: .8rem .3rem .7rem; }
  html.rail-collapsed .rail-brand-text { display: none; }
  html.rail-collapsed .rail-collapse { margin-left: 0; transform: rotate(180deg); }
  /* The other dashboard, as one glyph. */
  html.rail-collapsed .rail-switch { margin: .55rem .5rem .15rem; border: 0; }
  html.rail-collapsed .rail-switch-on { display: none; }
  html.rail-collapsed .rail-switch a { font-size: 0; padding: .45rem 0; border-radius: 6px; }
  html.rail-collapsed .rail-switch a::before { content: '\21C4'; font-size: .95rem; }
  /* A rule where a group's name was, so the groups still read as groups. */
  html.rail-collapsed .rail-group { height: 1px; margin: .5rem .8rem; padding: 0; overflow: hidden; font-size: 0; background: var(--line); }
  html.rail-collapsed .rail-link { justify-content: center; gap: 0; padding: .55rem 0; position: relative; }
  html.rail-collapsed .rail-glyph { width: auto; font-size: 1rem; }
  html.rail-collapsed .rail-label {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
  html.rail-collapsed .rail-foot { padding: .75rem .3rem .9rem; text-align: center; }
  html.rail-collapsed .rail-who { justify-content: center; font-size: 0; }
  html.rail-collapsed .rail-who a { display: none; }
  html.rail-collapsed .rail-who-dot { width: 9px; height: 9px; }
  html.rail-collapsed .rail-signout { font-size: 0; margin-top: .7rem; padding: .3rem 0; width: 100%; text-align: center; }
  html.rail-collapsed .rail-signout::before { content: '\23FB'; font-size: 1rem; }
}

@media (max-width: 860px) {
  .shell { display: block; }

  .rail {
    position: fixed; z-index: 60;
    top: 0; bottom: 0; left: 0;
    width: min(17.5rem, 86vw);
    height: 100vh; height: 100dvh;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform .2s ease, visibility 0s linear .2s;
  }
  html.rail-drawer-open .rail {
    transform: none;
    visibility: visible;
    transition: transform .2s ease, visibility 0s;
    box-shadow: 8px 0 32px rgba(0, 0, 0, .45);
  }
  .rail-backdrop {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(0, 0, 0, .5);
    opacity: 0; pointer-events: none;
    transition: opacity .2s ease;
  }
  html.rail-drawer-open .rail-backdrop { opacity: 1; pointer-events: auto; }
  html.rail-drawer-open body { overflow: hidden; }

  .rail-close { display: inline-flex; width: 40px; height: 40px; margin: -.3rem -.4rem -.3rem auto; font-size: 1rem; }
  .topbar .rail-menu {
    display: inline-flex; width: 40px; height: 40px; min-height: 40px;
    margin: -.2rem 0 -.2rem -.35rem;
    font-size: 1.15rem; color: var(--ink);
    background: var(--panel-2); border-color: var(--line);
  }
  .rail-link { min-height: 44px; padding: .5rem 1rem; }
  .rail .rail-switch > * { padding: .5rem .3rem; }
  .rail-signout { padding: .35rem 0; }

  .topbar { padding: .7rem .9rem; }
  .content { padding: 1rem .9rem 2rem; }
}

@media (prefers-reduced-motion: reduce) {
  .rail, .rail-backdrop { transition: none !important; }
}

/* ── Phones ──
   Below this the top bar's title and its buttons stop fitting on one line,
   and a heading squeezed to four characters beside three buttons is worse
   than a heading with the buttons under it. */
@media (max-width: 560px) {
  .topbar { align-items: center; gap: .5rem .7rem; }
  /* Beside the menu button, and the buttons on a line of their own below. */
  .topbar-title { flex: 1 1 0; min-width: 0; }
  .topbar-acts { margin-left: 0; width: 100%; flex-wrap: wrap; }
  .topbar-acts form { flex: 1 1 auto; }
  .topbar-acts .act-btn { width: 100%; }

  .content { padding: .8rem .7rem 2rem; }

  /* Touch targets. 28px is a mouse target. */
  .act, .act-btn { min-height: 38px; }
}


.summary { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.pill {
  font-size: .78rem; padding: .3rem .7rem; border-radius: 999px;
  border: 1px solid var(--line); color: var(--ink-dim); background: var(--panel);
}
.pill b { color: var(--ink); font-weight: 700; }
.pill.ok b   { color: var(--ok); }
.pill.info b { color: var(--accent); }
.pill.warn b { color: var(--warn); }
.pill.bad b  { color: var(--bad); }
/* Expected and neutral: said, never coloured. `.pill.off` (below) is the red
   of a disabled operator and was being reused for states that are simply how
   things are -- "no observer", "defaulted", "not applicable" -- which read as
   faults. Those use this. */
.pill.neutral, .pill.neutral b { color: var(--ink-dim); }
.pill.neutral { border-color: var(--line); }
.pill.muted b { color: var(--ink-dim); }

.grid { display: grid; gap: .8rem; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }

.store {
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: var(--radius);
  padding: .95rem 1rem 1rem;
}
.store.ok        { border-left-color: var(--ok); }
.store.attention { border-left-color: var(--warn); }
.store.down      { border-left-color: var(--bad); }

.store h2 { font-size: .95rem; font-weight: 700; display: flex; align-items: center; gap: .45rem; }
.store .url { font-family: var(--mono); font-size: .74rem; color: var(--ink-dim); word-break: break-all; margin-top: .1rem; }
.store .headline { margin-top: .55rem; font-size: .84rem; }
.store.ok .headline        { color: var(--ok); }
.store.attention .headline { color: var(--warn); }
.store.down .headline      { color: var(--bad); }

.dot { width: 8px; height: 8px; border-radius: 999px; flex-shrink: 0; }
.dot.ok { background: var(--ok); } .dot.attention { background: var(--warn); } .dot.down { background: var(--bad); }

/* The store page's two rows of facts -- Deployment's checks and Contents'
   counts -- are one kind of card with one rhythm (pass 3, 6.4): four to a
   row where they fit, two by two below 700px, never three and an orphan. */
.facts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .5rem; margin-top: .8rem; }
.fact { background: var(--panel-2); border-radius: 7px; padding: .5rem .6rem; }
.fact .n { font-size: .95rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.fact .l { font-size: .66rem; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .06em; }

.issues { margin-top: .75rem; display: flex; flex-direction: column; gap: .25rem; }
.issue { font-size: .78rem; color: var(--ink-dim); display: flex; gap: .4rem; }
.issue::before { content: '•'; color: var(--warn); }

.meta { margin-top: .8rem; padding-top: .6rem; border-top: 1px solid var(--line);
        display: flex; gap: .8rem; flex-wrap: wrap; font-size: .72rem; color: var(--ink-dim); font-family: var(--mono); }

.empty { border: 1px dashed var(--line); border-radius: var(--radius); padding: 2rem; text-align: center; color: var(--ink-dim); }
.empty code { font-family: var(--mono); color: var(--ink); }

/* login */
.login { max-width: 340px; margin: 12vh auto; }
.login h1 { font-size: 1rem; margin-bottom: .2rem; }
.login p { color: var(--ink-dim); font-size: .82rem; margin-bottom: 1.1rem; }
.card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.2rem; }
label { display: block; font-size: .78rem; color: var(--ink-dim); margin-bottom: .3rem; }
/* ── Every control, once ──
   This used to be enumerated per container: the login card, then .op-acts,
   then .op-new, then .audit-filter, each re-declaring the same six
   properties. Every new form since has arrived unstyled and been noticed only
   when somebody looked at it -- the reply box on Messages was a white 171px
   rectangle on a near-black page for exactly that reason.

   A control is a control. The type list is explicit rather than a bare
   `input` so checkboxes and radios keep their native rendering. */
input[type=text],
input[type=email],
input[type=password],
input[type=search],
input[type=url],
input[type=number],
input[type=date],
select,
textarea {
  padding: .4rem .6rem;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ff);
  font-size: .86rem;
  line-height: 1.45;
  max-width: 100%;
}
textarea { width: 100%; resize: vertical; min-height: 5rem; }

input[type=text]:focus,
input[type=email]:focus,
input[type=password]:focus,
input[type=search]:focus,
input[type=url]:focus,
input[type=number]:focus,
input[type=date]:focus,
select:focus,
textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

input::placeholder, textarea::placeholder { color: var(--ink-dim); opacity: .6; }
select:disabled, input:disabled, textarea:disabled { opacity: .4; cursor: not-allowed; }

/* The login card's fields are full width and a little larger. */
.login input { width: 100%; padding: .55rem .7rem; border-radius: 7px; font-size: .9rem; }
/* ── Scoped to the login form ──
   This was a bare `button` selector: a full-width, accent-filled, top-margined
   block, applied to every button in the console. It is what pushed the theme
   toggle six pixels below the controls beside it, and what made a two-word
   label in the top bar wrap into a two-line button. It belongs to the one
   form it was written for. */
.login button {
  margin-top: .8rem; width: 100%; padding: .58rem 1rem; border: 0; border-radius: 7px;
  background: var(--accent); color: var(--on-accent); font-weight: 600; font-size: .88rem;
  font-family: var(--ff); cursor: pointer;
}
.login button:hover { background: var(--accent-hover); }
/* Why the last session ended: information, not an error. */
.login-ended { font-size: .84rem; color: var(--ink-dim); margin: 0 0 .9rem; line-height: 1.5; }
.err { background: rgba(228,99,92,.12); border: 1px solid var(--bad); color: var(--bad);
       padding: .5rem .7rem; border-radius: 7px; font-size: .82rem; margin-bottom: .8rem; }

/* -- Update controls ------------------------------------------ */
.act-btn {
  width: auto; margin: 0; padding: .3rem .7rem;
  background: none; border: 1px solid var(--line); border-radius: 6px;
  color: var(--ink-dim); font-size: .82rem; font-weight: 500;
  font-family: var(--ff); cursor: pointer;
}
.act-btn:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); background: none; }
.act-btn:disabled { opacity: .4; cursor: not-allowed; }
.deploy-form { margin-top: .7rem; }
.deploy-state { margin-top: .7rem; font-size: .78rem; color: var(--ink-dim); font-family: var(--mono); }
.deploy-state.bad { color: var(--bad); }
.flash {
  border-radius: 7px; padding: .5rem .75rem; margin-bottom: .5rem; font-size: .84rem;
  border: 1px solid var(--line); background: var(--panel);
}
.flash.ok  { border-color: var(--ok);  color: var(--ok); }
.flash.bad { border-color: var(--bad); color: var(--bad); }

/* -- New store runbook ---------------------------------------- */
.newstore-form {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.15rem 1.2rem 1.2rem;
  margin-bottom: 1.4rem;
}

/* Two groups, because these are two different jobs: naming the store, and
   the server facts you go and fetch from cPanel. */
.ns-section + .ns-section { margin-top: 1.15rem; padding-top: 1.15rem; border-top: 1px solid var(--line); }
.ns-legend {
  display: block; margin-bottom: .6rem;
  font-size: .66rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-dim);
}

/* Explicit tracks, not auto-fit. Auto-fit gave every field the same width
   regardless of what goes in it, which orphaned a two-digit number onto its
   own row and truncated the longest value on the form. */
.ns-row { display: grid; gap: 1rem; align-items: start; }
.ns-row-store  { grid-template-columns: 1fr 1fr 1fr; }
.ns-row-server { grid-template-columns: 1.5fr 1fr 7.5rem; }

.ns-field { min-width: 0; }
.ns-field label {
  display: block; margin-bottom: .3rem;
  font-size: .76rem; font-weight: 500; color: var(--ink);
}
.ns-field input {
  width: 100%; padding: .5rem .65rem; border-radius: 6px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  font-family: var(--ff); font-size: .88rem; line-height: 1.3;
}
.ns-field input::placeholder { color: var(--ink-dim); opacity: .55; }
.ns-field input:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: transparent; }
.ns-field-narrow input {
  font-variant-numeric: tabular-nums;
  /* Capped rather than stretched: a two-digit hour in a full-width box
     reads as a field expecting far more than it wants. */
  max-width: 7.5rem;
}

/* Reserved height so a one-line hint beside a two-line hint does not leave
   the fields sitting at different heights. */
.ns-hint {
  display: block; margin-top: .3rem; min-height: 2.1em;
  font-size: .7rem; line-height: 1.5; color: var(--ink-dim);
}

.ns-actions { display: flex; align-items: center; gap: .9rem; margin-top: 1.1rem; flex-wrap: wrap; }
.ns-note { font-size: .74rem; color: var(--ink-dim); }
.btn-go {
  width: auto; margin: 0; padding: .5rem 1.1rem; border: 0; border-radius: 6px;
  background: var(--accent); color: var(--on-accent); font-weight: 600; font-size: .85rem;
  font-family: var(--ff); cursor: pointer;
}
.btn-go:hover { background: var(--accent-hover); }

@media (max-width: 760px) {
  .ns-row-store, .ns-row-server { grid-template-columns: 1fr; }
  .ns-hint { min-height: 0; }
}

.step { border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); padding: 1rem 1.1rem; margin-bottom: .8rem; }
.step h2 { font-size: .92rem; font-weight: 700; margin-bottom: .5rem; }
.step h3 { font-size: .8rem; font-weight: 600; color: var(--ink-dim); margin: .9rem 0 .35rem; }
.step .note { font-size: .82rem; color: var(--ink-dim); margin: .4rem 0; line-height: 1.55; }
.step code { font-family: var(--mono); font-size: .78rem; color: var(--ink); background: var(--panel-2); padding: .1rem .3rem; border-radius: 4px; }
.step ul.plain { list-style: none; display: flex; flex-direction: column; gap: .35rem; font-size: .84rem; color: var(--ink-dim); }
.step ul.plain b { color: var(--ink); }
.step a { color: var(--accent); }

.cmd { position: relative; margin: .5rem 0; }
.cmd pre {
  background: var(--bg); border: 1px solid var(--line); border-radius: 7px;
  padding: .6rem 4.5rem .6rem .7rem; overflow-x: auto;
  font-family: var(--mono); font-size: .76rem; color: var(--ink); line-height: 1.6;
  white-space: pre-wrap; word-break: break-all;
}
.cmd .copy {
  position: absolute; top: .4rem; right: .4rem; width: auto; margin: 0;
  padding: .2rem .55rem; border: 1px solid var(--line); border-radius: 5px;
  background: var(--panel); color: var(--ink-dim);
  font-size: .7rem; font-family: var(--ff); cursor: pointer;
}
.cmd .copy:hover { color: var(--accent); border-color: var(--accent); background: var(--panel); }
.cmd .copy.done { color: var(--ok); border-color: var(--ok); }

/* Phase 2A provisioning. The visual hierarchy follows the operator's next
   action: warning, current action, then completed/waiting context. */
.provisioning-warning {
  display: grid; gap: .2rem; margin-bottom: 1rem; padding: .85rem 1rem;
  border: 1px solid var(--warn); border-left-width: 4px; border-radius: var(--radius);
  background: rgba(224,163,62,.08);
}
.provisioning-warning strong { color: var(--warn); }
.provisioning-warning span { color: var(--ink-dim); font-size: .8rem; }
.provisioning-request { margin-bottom: 1rem; }
.provisioning-check { display: flex; gap: .7rem; align-items: flex-start; margin: .8rem 0; cursor: pointer; }
.provisioning-check input { width: auto; margin-top: .2rem; }
.provisioning-check span, .provisioning-check small { display: block; }
.provisioning-check small { margin-top: .12rem; color: var(--ink-dim); }
.manual-fallback { display: flex; gap: .7rem; align-items: center; flex-wrap: wrap; padding: .9rem 1rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); }
.manual-fallback span { color: var(--ink-dim); font-size: .8rem; }
.manual-fallback .act { margin-left: auto; }
.prov-preflight { margin: .9rem 0; padding: .8rem 1rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel-2); }
.prov-preflight p { margin: .25rem 0 .6rem; color: var(--ink-dim); font-size: .78rem; }
.prov-preflight code { display: block; margin: .2rem 0; overflow-wrap: anywhere; color: var(--ink); font-size: .75rem; }
.optional { color: var(--ink-dim); font-size: .7rem; font-weight: 400; }
.prov-summary { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: .8rem; margin-bottom: 1rem; }
.prov-summary > div { padding: .9rem 1rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); }
.prov-summary span,.prov-summary small { display: block; color: var(--ink-dim); font-size: .7rem; }
.prov-summary strong { display: block; margin: .2rem 0; font-size: 1rem; }
.prov-progress { height: 5px; margin-top: .55rem; overflow: hidden; border-radius: 99px; background: var(--panel-2); }
.prov-progress i { display: block; height: 100%; background: var(--accent); }
.prov-step { position: relative; border-left-width: 4px; }
.prov-step.completed { border-left-color: var(--ok); }
.prov-step.failed { border-left-color: var(--bad); }
.prov-step.prov-action-required { border-color: var(--accent); border-left-width: 4px; }
.prov-step-head { display: flex; align-items: center; gap: .7rem; }
.prov-step-head h2 { margin: 0; }
.prov-step-head small { color: var(--ink-dim); font: .65rem var(--mono); }
.prov-marker { display: grid; place-items: center; width: 1.8rem; height: 1.8rem; flex: 0 0 auto; border: 1px solid var(--line); border-radius: 50%; color: var(--ink-dim); font-weight: 700; }
.completed .prov-marker { border-color: var(--ok); color: var(--ok); }
.failed .prov-marker { border-color: var(--bad); color: var(--bad); }
.prov-step-state { margin-left: auto; color: var(--ink-dim); font: 700 .62rem var(--mono); letter-spacing: .08em; }
.prov-action-required .prov-step-state { color: var(--accent); }
.prov-step-action { margin-top: .8rem; }
.prov-problem { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.prov-problem select { width: auto; min-width: 14rem; }
.prov-failure { margin-top: .6rem; color: var(--bad); font-size: .8rem; }
.prov-report { margin: 1rem 0; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); overflow: hidden; }
.prov-report-head { display: flex; justify-content: space-between; align-items: center; padding: .9rem 1rem; border-bottom: 1px solid var(--line); }
.prov-report-head span,.prov-report-head small { color: var(--ink-dim); font-size: .7rem; }
.prov-report-head h2 { margin: .1rem 0 0; }
.prov-check { display: grid; grid-template-columns: 6.5rem 1fr; gap: .8rem; align-items: start; padding: .65rem 1rem; border-top: 1px solid var(--line-soft); }
.prov-check:first-of-type { border-top: 0; }
.prov-check strong,.prov-check small { display: block; }
.prov-check small { margin-top: .1rem; color: var(--ink-dim); font-size: .72rem; }
.prov-result,.prov-state { display: inline-block; width: fit-content; padding: .14rem .42rem; border-radius: 4px; border: 1px solid var(--line); color: var(--ink-dim); font: 700 .62rem var(--mono); text-transform: uppercase; }
.prov-result.pass,.prov-state.ready_for_owner { color: var(--ok); border-color: var(--ok); }
.prov-result.fail,.prov-state.failed { color: var(--bad); border-color: var(--bad); }
.prov-result.warning,.prov-result.manual { color: var(--warn); border-color: var(--warn); }
.prov-ready { border-color: var(--ok); }
.prov-artifacts { margin: 1rem 0; padding: .8rem 1rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); }
.prov-artifacts summary { cursor: pointer; color: var(--ink-dim); }
.prov-artifacts dl { display: grid; grid-template-columns: minmax(9rem,auto) 1fr; gap: .35rem .8rem; margin-top: .8rem; }
.prov-artifacts dt { color: var(--ink-dim); font-size: .72rem; text-transform: uppercase; }
.prov-artifacts dd { min-width: 0; overflow-wrap: anywhere; font-family: var(--mono); font-size: .75rem; }
.provisioning-table td small { display: block; color: var(--ink-dim); }
.merchant-signup-table tr.needs-attention td { background: color-mix(in srgb, var(--bad) 8%, transparent); }
.merchant-signup-table form { margin: 0; }

@media (max-width: 760px) {
  .prov-summary { grid-template-columns: 1fr; }
  .prov-check { grid-template-columns: 1fr; }
  .manual-fallback .act { margin-left: 0; }
}

/* -- Plan and lifecycle --------------------------------------- */
.plan {
  margin-left: auto; font-size: .62rem; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; padding: .12rem .42rem; border-radius: 999px;
  border: 1px solid var(--line); color: var(--ink-dim);
}
.plan-paid     { color: var(--ok);   border-color: var(--ok); }
.plan-beta     { color: var(--accent); border-color: var(--accent); }
.plan-internal,
.plan-free     { color: var(--ink-dim); }

.row-actions { display: flex; gap: .4rem; margin-top: .7rem; flex-wrap: wrap; }
.row-actions .deploy-form { margin: 0; }
.act-btn.close:hover:not(:disabled)  { color: var(--bad); border-color: var(--bad); }
.act-btn.reopen { color: var(--ok); border-color: var(--ok); }

.closed-banner {
  margin-top: .55rem; padding: .35rem .6rem; border-radius: 6px;
  background: rgba(228,99,92,.12); border: 1px solid var(--bad);
  color: var(--bad); font-size: .78rem; font-weight: 600;
}
.billing { margin-top: .6rem; font-size: .78rem; font-family: var(--mono); color: var(--ok); }
.billing.bad { color: var(--bad); font-weight: 600; }
/* A complimentary shop: said plainly, in the ordinary text colour -- it is a
   fact about the shop, not a fault -- with the one action that ends it. */
.billing.comp { color: var(--ink); font-family: inherit; font-size: .84rem; line-height: 1.5; }
.comp-end { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .8rem; margin-top: .4rem; }
.comp-end label { display: inline-flex; align-items: flex-start; gap: .4rem; font-size: .8rem; color: var(--ink-dim); }
.comp-end input[type=checkbox] { margin-top: .2rem; }

/* -- Store detail --------------------------------------------- */
.store h2 a:hover { color: var(--accent); }
.facts-wide { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .5rem; margin: .8rem 0; }
.fct { background: var(--panel-2); border-radius: 7px; padding: .5rem .6rem; }
.fct-v { display: block; font-size: .82rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.fct-l { display: block; font-size: .7rem; color: var(--ink-dim); margin-top: .1rem; }
.fct.ok  .fct-v { color: var(--ok); }
.fct.bad .fct-v { color: var(--bad); }

.dep { border: 1px solid var(--line); border-radius: 7px; margin-bottom: .45rem; background: var(--bg); }
.dep summary {
  cursor: pointer; padding: .5rem .65rem; display: flex; gap: .6rem; align-items: baseline;
  flex-wrap: wrap; font-size: .78rem; color: var(--ink-dim); font-family: var(--mono);
}
.dep summary::-webkit-details-marker { display: none; }
.dep-status { font-weight: 700; text-transform: uppercase; font-size: .68rem; letter-spacing: .05em; }
.dep-done    { color: var(--ok); }
.dep-failed  { color: var(--bad); }
.dep-pending,
.dep-running { color: var(--warn); }
.dep-action { color: var(--ink); }
.dep-exit { color: var(--bad); }
.dep-out {
  margin: 0 .65rem .65rem; padding: .6rem .7rem; border-radius: 6px;
  background: var(--panel-2); color: var(--ink); font-family: var(--mono);
  font-size: .73rem; line-height: 1.55; white-space: pre-wrap; word-break: break-word;
  max-height: 26rem; overflow: auto;
}

/* ── The platform's own two apps ─────────────────────────────
   Below the store roster, and visually quieter than it: this is a thing you
   do occasionally, not the question the page exists to answer. */
.platform { display: grid; gap: .6rem; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); }
.platform-app {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  border: 1px solid var(--line); border-radius: 8px;
  padding: .7rem .9rem; background: var(--panel);
}
.platform-app .url { font-size: .78rem; color: var(--ink-dim); margin-top: .15rem; }
.platform-app form { margin: 0; }
.platform-note { margin-top: .7rem; font-size: .78rem; color: var(--ink-dim); max-width: 46rem; }

/* A shop the owner has closed. Deliberately quieter than .closed-banner: one
   is something we did and may need undoing, the other is a shop being set up. */
.owner-closed-banner {
  margin-top: .55rem; padding: .35rem .6rem; border-radius: 6px;
  background: rgba(228, 178, 92, .10); border: 1px solid rgba(228, 178, 92, .45);
  color: var(--ink-dim); font-size: .78rem; font-weight: 600;
}

/* ── Touch ────────────────────────────────────────────────────
   Keyed to the pointer rather than to a width, matching the shop admin and
   the marketplace: a tablet in portrait is 768px and is still a finger, and
   this console is a dense working surface on a desktop by design. */
@media (pointer: coarse) {
  button,
  .act-btn,
  .btn-go,
  input[type=password],
  .login input[type=email],
  .ns-field input,
  .top a.act {
    min-height: 44px;
  }
  .act-btn, .btn-go, .top a.act {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* ── Who you are ──────────────────────────────────────────────
   Under one shared password this would have been noise. With accounts and
   roles it is load-bearing: two people see different pages, and everything
   done from here is recorded against whoever this names. */
.whoami {
  font-size: .78rem; color: var(--ink-dim); font-family: var(--mono);
  padding-left: .8rem; border-left: 1px solid var(--line); white-space: nowrap;
}
.whoami a:hover { color: var(--accent); }
/* Full privileges, no name on anything it does. Marked, not hidden. */
.whoami.shared { color: var(--warn); }

/* ── Operators ────────────────────────────────────────────────
   Three columns: who, what they are, what you can do about it. The actions
   sit in their own column rather than under the row, so scanning down the
   list never crosses a control. */
.op {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: .3rem 1rem;
  padding: .85rem 0;
  border-top: 1px solid var(--line);
}
.op:first-of-type { border-top: 0; }
/* Dimmed rather than removed. This list is how somebody gets re-enabled. */
.op-off { opacity: .55; }

.op-who    { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.op-email  { font-weight: 600; font-size: .88rem; word-break: break-all; }
.op-name   { font-size: .8rem; color: var(--ink-dim); }
.op-you    { font-size: .68rem; color: var(--accent); font-weight: 500; }
.op-who .meta { margin-top: .25rem; padding-top: 0; border-top: 0; }

.op-state  { display: flex; align-items: flex-start; gap: .35rem; flex-wrap: wrap; }
.pill.role-super_admin { color: var(--accent); border-color: var(--accent); }
.pill.off  { color: var(--bad); border-color: var(--bad); }

.op-acts {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; align-items: center; gap: .45rem;
}
.op-acts form { display: flex; align-items: center; gap: .35rem; }
.op-acts button { margin-top: 0; }

/* These sit in tight rows beside buttons, so they run a size smaller than
   the shared default. Everything else about them comes from that default. */
.op-acts input, .op-acts select,
.op-new  input, .op-new  select,
.audit-filter input, .audit-filter select {
  padding: .3rem .5rem;
  font-size: .82rem;
}

.op-new {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: .7rem .8rem;
  align-items: end;
  margin: .7rem 0;
}
.op-new label { margin-bottom: 0; }
.op-new label span { display: block; margin-bottom: .25rem; }
.op-new input, .op-new select { width: 100%; padding: .5rem .6rem; font-size: .86rem; }
/* The only full-width accent button on the page, because it is the only
   action here that creates something. */
.op-new button { grid-column: 1 / -1; margin-top: .2rem; }

/* ── Invitations ──────────────────────────────────────────────
   The Operators pattern: a form that creates one thing, then a list of rows
   with their state and the one action each can take. */
.invite-new { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: .7rem .8rem; align-items: start; margin: .7rem 0; }
.invite-new label { margin-bottom: 0; }
.invite-new label span { display: block; margin-bottom: .25rem; }
.invite-new label small { display: block; margin-top: .25rem; font-size: .74rem; color: var(--ink-dim); }
.invite-new input, .invite-new select { width: 100%; padding: .5rem .6rem; font-size: .86rem; }
.invite-new .invite-note, .invite-new button, .invite-kind { grid-column: 1 / -1; }
/* The no-card switch: a checkbox, not a full-width field. */
.invite-new .invite-check input { width: auto; padding: 0; }
/* `label { display: block }` outranks the hidden attribute's own rule. */
.invite-new [hidden] { display: none; }
.invite-kind { border: 0; padding: 0; margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: .4rem 1.1rem; }
.invite-kind legend { font-size: .78rem; color: var(--ink-dim); margin-bottom: .3rem; }
.invite-kind label { display: inline-flex; align-items: center; gap: .35rem; font-size: .86rem; color: var(--ink); }
.invite-kind .note { flex-basis: 100%; margin: 0; }
.invite-how { margin: .3rem 0 0 1.1rem; display: flex; flex-direction: column; gap: .35rem; font-size: .84rem; color: var(--ink-dim); line-height: 1.5; }
.invite-how b { color: var(--ink); }
.invite-fresh { border-color: var(--accent); }
.invite-link { display: flex; gap: .5rem; align-items: center; }
.invite-link input { flex: 1; min-width: 0; font-family: var(--mono); font-size: .78rem; }
.invite-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: .3rem .8rem; align-items: start; padding: .75rem 0; border-top: 1px solid var(--line); }
.invite-row:first-child { border-top: 0; }
.invite-who { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.invite-label { font-weight: 600; font-size: .88rem; word-break: break-all; }
.invite-who .meta { margin-top: 0; padding-top: 0; border-top: 0; font-family: inherit; font-size: .78rem; color: var(--ink-dim); }
.invite-revoke button { margin-top: 0; }
@media (max-width: 560px) {
  .invite-row { grid-template-columns: minmax(0, 1fr) auto; }
  .invite-revoke { grid-column: 1 / -1; }
}

/* ── Audit ────────────────────────────────────────────────────
   A time column and a body, so the eye can run down the timestamps without
   the rest of the line moving under it. */
.audit-filter { display: flex; flex-wrap: wrap; align-items: end; gap: .6rem; }
.audit-filter label { margin-bottom: 0; }
.audit-filter label span { display: block; margin-bottom: .25rem; }
.audit-filter button { margin-top: 0; }
a.act-btn { display: inline-flex; align-items: center; text-decoration: none; }

.audit-entry {
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr);
  gap: 0 1rem;
  padding: .6rem 0;
  border-top: 1px solid var(--line);
}
.audit-entry:first-of-type { border-top: 0; }
.audit-when   { font-family: var(--mono); font-size: .74rem; color: var(--ink-dim); white-space: nowrap; }
.audit-body   { min-width: 0; }
.audit-line   { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; }
.audit-action { font-family: var(--mono); font-size: .82rem; font-weight: 600; }
.audit-target { font-size: .82rem; color: var(--ink-dim); word-break: break-all; }
/* A refusal is information, not an error to be shouted about — one attempt is
   a typo, forty are an attack, and the count is what tells them apart. */
.audit-failed .audit-action { color: var(--warn); }
.audit-delta  { margin-top: .2rem; font-size: .78rem; color: var(--ink-dim); font-family: var(--mono); }
.audit-key      { color: var(--ink); }
.audit-arrow  { color: var(--accent); }
.audit-sep    { padding: 0 .45rem; opacity: .5; }
.audit-entry .meta  { margin-top: .25rem; padding-top: 0; border-top: 0; }
.audit-shared { color: var(--warn); }
.audit-kind { font-size: .66rem; text-transform: uppercase; letter-spacing: .04em; padding: .05rem .35rem; border-radius: 4px; border: 1px solid var(--line-soft); color: var(--ink-dim); background: var(--panel-2); margin-right: .2rem; }

@media (max-width: 640px) {
  .audit-entry { grid-template-columns: 1fr; gap: .15rem; }
  .op { grid-template-columns: 1fr; }
}

/* ── Security ─────────────────────────────────────────────────
   Severity is one scale, used by every table on the page. Four steps, and the
   neutral is genuinely neutral: most of what a shop reports is bots finding a
   form, and a page that colours that as a problem is a page nobody trusts. */
.sev { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: .5rem; vertical-align: middle; }
.sev-info   { background: var(--ink-dim); opacity: .5; }
.sev-low    { background: #6B8CA8; }
.sev-medium { background: var(--warn); }
.sev-high   { background: var(--bad); }

.sec-table { width: 100%; border-collapse: collapse; margin-top: .6rem; font-size: .84rem; }
.sec-table th {
  text-align: left; font-size: .68rem; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-dim);
  padding: 0 .6rem .4rem 0; border-bottom: 1px solid var(--line);
}
.sec-table td { padding: .45rem .6rem .45rem 0; border-bottom: 1px solid var(--line); }
.sec-table tr:last-child td { border-bottom: 0; }
.sec-table .num { text-align: right; font-variant-numeric: tabular-nums; padding-right: 0; }
.sec-table th.num { padding-right: 0; }
.mono { font-family: var(--mono); font-size: .8rem; }

/* One shop's security standing. Same three-part shape as a roster card --
   who, the numbers, the small print -- so an operator moving between the two
   pages is reading the same object in the same order. */
.sq { padding: .8rem 0; border-top: 1px solid var(--line); }
.sq:first-of-type { border-top: 0; padding-top: 0; }
.sq-who { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.sq-name { font-weight: 600; font-size: .95rem; }
.sq-name a:hover { color: var(--accent); }
.sq-headline { font-size: .84rem; color: var(--ink-dim); }
.sq-nums { display: flex; gap: 1.2rem; flex-wrap: wrap; margin-top: .45rem; }
.sq-n { font-size: .78rem; color: var(--ink-dim); }
.sq-n b { font-size: .95rem; color: var(--ink); font-variant-numeric: tabular-nums; }
.sq-n.warn b { color: var(--warn); }
.sq-n.bad  b { color: var(--bad); }
.sq .meta { margin-top: .5rem; }

/* The security line on a store's own page. */
.sec-card { display: flex; align-items: baseline; gap: .8rem; flex-wrap: wrap; }
.sec-card .sq-nums { margin-top: 0; }

/* .dot is sized but has no display of its own — it works inside the flex
   headings it was written for, and collapses to nothing anywhere else. These
   two places are not flex containers, so it needs saying. */
.sq-name .dot,
.step h2 .dot { display: inline-block; margin-right: .45rem; vertical-align: middle; }

/* Several small buttons in a table cell. They wrapped into a column and made
   the row three lines tall. */
.q-acts { white-space: nowrap; }
.q-acts form { display: inline-flex; margin-right: .3rem; }

/* ── Messages: two panes ──
   The queue stays on screen while a conversation is open, so reading one and
   going to the next is not a round trip through a page that forgot where you
   were. Same arrangement as the shop admin's Messages, deliberately. */
.msg-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: 1rem; align-items: start; }
.msg-side, .msg-main { min-width: 0; }
.msg-side .q { font-size: .82rem; }
.msg-empty {
  border: 1px dashed var(--line); border-radius: var(--radius);
  padding: 2.4rem 1.4rem; text-align: center; color: var(--ink-dim);
}
.msg-empty-mark { font-family: var(--mono); font-size: 1.6rem; color: var(--accent); margin-bottom: .5rem; }
.msg-empty h3 { font-size: .95rem; color: var(--ink); margin-bottom: .3rem; }
@media (max-width: 1100px) {
  .msg-layout { grid-template-columns: 1fr; }
}

/* The Marketplace page's note under Shops (pass 3, 6.5): room above it,
   and a lead with three short points instead of one dense paragraph. */
.mk-how { margin: 1rem 0 0; max-width: 46rem; font-size: .82rem; line-height: 1.55; color: var(--ink-dim); }
.mk-how p { margin: 0 0 .35rem; }
.mk-how ul { margin: 0; padding-left: 1.1rem; display: grid; gap: .3rem; }
.mk-how b { color: var(--ink); font-weight: 600; }

/* ── Data tables ──────────────────────────────────────────────
   table.q is used by the marketplace queues and the message list and had no
   styling of its own at all: browser defaults on a dark panel, which is why
   those pages read as unfinished next to the store roster. One definition,
   used by both. */
.q {
  width: 100%;
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  font-size: .84rem;
}
.q thead th {
  padding: .5rem .7rem;
  text-align: left;
  background: var(--panel-2);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-dim);
  white-space: nowrap;
}
.q td {
  padding: .6rem .7rem;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
}
.q tbody tr:last-child td { border-bottom: 0; }
.q tbody tr:hover { background: var(--panel-2); }
.q a { color: var(--ink); border-bottom: 1px solid transparent; }
.q a:hover { color: var(--accent); border-bottom-color: var(--accent); }
/* The second line under a name: the email, the URL, the count. */
.q .who { font-size: .72rem; color: var(--ink-dim); margin-top: .1rem; }
.q .clock { font-family: var(--mono); white-space: nowrap; font-variant-numeric: tabular-nums; }
.q .clock.soon { color: var(--warn); }
.q .clock.late { color: var(--bad); font-weight: 700; }
.q tr.late { background: rgba(228, 99, 92, .06); }

/* A section heading over a table, so three queues on one page read as three
   things rather than one long scroll. */
h2.sec {
  /* Generous above, tight below: the heading belongs to what follows it, and
     on the store page this one lands under a grid of cards that needs the
     separation to read as a new section rather than a caption on the last
     card. */
  margin: 2.6rem 0 .6rem;
  font-family: var(--mono);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
/* Only when nothing precedes it. As :first-of-type this also caught the
   store page's single heading, which follows a grid of cards and is exactly
   the one that needs the space. */
.content > h2.sec:first-child { margin-top: 0; }
.mod-count {
  display: inline-block; margin-left: .4rem;
  padding: 0 .35rem; border-radius: 999px;
  background: var(--accent-dim); color: var(--accent);
  font-size: .64rem; font-weight: 700;
}

/* ── Message filters ──
   Open and Closed, with counts, beside the list they filter. This was a link
   in the top bar reading "Include closed" that reloaded the page into a
   different mode -- findable only if you already knew it was there. */
.msg-filters { display: flex; gap: .35rem; margin-bottom: .7rem; }
.msg-filter {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .25rem .6rem;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--panel);
  font-size: .78rem; color: var(--ink-dim);
}
.msg-filter:hover { color: var(--ink); border-color: var(--ink-dim); }
.msg-filter.on { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
.msg-filter-n { font-family: var(--mono); font-size: .68rem; font-weight: 700; opacity: .8; }


/* ── A conversation ───────────────────────────────────────────
   .thread and the message bubbles inside it never had any styling at all.
   On a single-column page that read as plain text and passed unnoticed; in
   two panes it is half the screen, and the reply box was the browser's own
   white rectangle sitting on a near-black ground. */
.thread {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.1rem 1.1rem;
}
.thread-head {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  padding-bottom: .75rem; margin-bottom: .9rem;
  border-bottom: 1px solid var(--line);
}
.thread-head h2 { font-size: .98rem; font-weight: 700; letter-spacing: -.01em; }
.thread-head .who { font-size: .78rem; color: var(--ink-dim); }

/* Who said what, told by side and by ground rather than by a label alone.
   Theirs sits left on the panel's own surface; ours sits right, tinted with
   the accent -- the same arrangement the shop admin uses for the other half
   of this conversation, so the two ends of it look like one thread. */
.thread .m {
  max-width: 42rem;
  padding: .6rem .8rem;
  margin-bottom: .6rem;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--panel-2);
}
.thread .m.operator {
  margin-left: auto;
  background: var(--accent-dim);
  border-color: var(--accent);
}
.thread .m .hd {
  font-size: .72rem;
  font-weight: 700;
  color: var(--ink-dim);
  margin-bottom: .25rem;
}
.thread .m .bd {
  font-size: .86rem;
  line-height: 1.6;
  /* The body is stored with its newlines; pre-wrap keeps them without the
     nl2br that would double every one of them. */
  white-space: pre-wrap;
  word-break: break-word;
}
.thread .m:last-of-type { margin-bottom: 0; }

.thread .reply {
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
}
.thread .reply textarea { min-height: 6rem; font-size: .88rem; }
.thread-acts {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-top: .7rem;
}
/* Moved out of .thread-acts, where it was first written. Every form on the
   console has one button that is the thing you came to do and some that are
   second thoughts, and the distinction is not particular to a reply box --
   Create account on Operators had no styling at all, because the bare `button`
   rule that used to dress it was scoped to the login card and nothing replaced
   it here. */
.act-btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}
.act-btn.primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--on-accent); }
.thread-acts .spacer { flex: 1; }
.thread-acts .act { font-size: .8rem; color: var(--ink-dim); }
.thread-acts .act:hover { color: var(--accent); }
.thread-state {
  padding: .1rem .45rem; border-radius: 999px;
  font-family: var(--mono); font-size: .64rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
}
.thread-state.ours    { background: rgba(224, 163, 62, .16); color: var(--warn); }
.thread-state.theirs  { background: var(--panel-2); color: var(--ink-dim); }
.thread-state.settled { background: rgba(63, 185, 132, .14); color: var(--ok); }

/* ── The queue, as a list ──
   It was a five-column table sharing the screen with a conversation, so every
   cell wrapped onto three lines. One block per conversation, with the clock
   where the eye lands because it is the only part that ever changes urgency. */
.qlist { display: flex; flex-direction: column; gap: .3rem; }
.qitem {
  display: block;
  padding: .55rem .7rem;
  border: 1px solid transparent;
  border-left: 2px solid transparent;
  border-radius: 7px;
  color: inherit;
}
.qitem:hover { background: var(--panel-2); }
.qitem.on { background: var(--accent-dim); border-color: var(--accent); }
.qitem.late { border-left-color: var(--bad); }

.qitem-top { display: flex; align-items: baseline; gap: .5rem; }
.qitem-subject {
  flex: 1; min-width: 0;
  font-size: .88rem; font-weight: 600; line-height: 1.35;
  overflow-wrap: anywhere;
}
.qitem-clock {
  flex: none;
  font-family: var(--mono); font-size: .7rem; font-variant-numeric: tabular-nums;
  color: var(--ink-dim);
}
.qitem-clock.soon { color: var(--warn); }
.qitem-clock.late { color: var(--bad); font-weight: 700; }
.qitem-store { display: block; margin-top: .1rem; font-size: .78rem; color: var(--ink); opacity: .8; }
.qitem-meta  { display: block; margin-top: .05rem; font-size: .72rem; color: var(--ink-dim); }


/* ── Tables on a narrow screen ────────────────────────────────
   Every queue and log in this console is a table, and a table is the one
   thing that cannot be made narrow. Rather than restack each of them into
   cards -- six different shapes to keep in step -- each scrolls inside its
   own box, so the page itself never scrolls sideways and nothing is
   unreachable.

   The wrapper is applied to the table's own parent by a small script in the
   shell, so a table added later is covered without anybody remembering. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-scroll > .q,
.table-scroll > .sec-table { min-width: 34rem; }

@media (max-width: 700px) {
  /* Two panes become one: a conversation next to a queue needs both to be
     readable, and at this width neither would be. */
  .msg-layout { grid-template-columns: 1fr; }

  .q thead th, .q td { padding: .45rem .55rem; }
  .thread { padding: .8rem .8rem .9rem; }
  .thread .m { max-width: none; }
  .thread-acts { gap: .4rem; }
  .thread-acts .act-btn { flex: 1 1 auto; justify-content: center; }
  .thread-acts .spacer { display: none; }
  .thread-acts .act { width: 100%; text-align: center; padding-top: .3rem; }

  /* Buttons in a table cell stack rather than force the row wider. */
  .q-acts { white-space: normal; }
  .q-acts form { margin: 0 .3rem .3rem 0; }

  /* The store roster is already auto-fill; the platform pair is not. */
  .platform { grid-template-columns: 1fr; }
  .facts, .facts-wide { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}


/* ── The two dashboards ─────────────────────────────────────────
   The same control the marketplace's moderation rail carries, the other way
   round. Under the brand rather than in the top bar: it says where you are,
   which is what the rail is already for, and it is not an action on this page.

   The two halves are written to the same width so the pair does not jump as
   the active side changes between the two applications. */
.rail-switch {
  display: flex;
  margin: .7rem .8rem .2rem;
  border: 1px solid var(--line);
  border-radius: 7px;
  overflow: hidden;
  font-size: .74rem;
}
.rail-switch > * {
  flex: 1 1 0;
  padding: .34rem .3rem;
  text-align: center;
  white-space: nowrap;
  color: var(--ink-dim);
}
.rail-switch .rail-switch-on {
  background: var(--accent-dim);
  color: var(--ink);
  font-weight: 600;
}
.rail-switch a:hover { background: var(--panel-2); color: var(--ink); }



/* ── Signing in ─────────────────────────────────────────────────
   The passkey road on the login card, and the controls on the Signing in
   page. The same shapes as the marketplace's, because they are the same two
   features and an operator moving between the two should not have to relearn
   where anything is. */

.sec-passkey-btn {
  display: block;
  width: 100%;
  padding: .7rem 1rem;
  border: 1px solid var(--accent);
  border-radius: 7px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--ff);
  font-size: .92rem;
  font-weight: 600;
  cursor: pointer;
}
.sec-passkey-btn:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
.sec-passkey-btn:disabled { opacity: .5; cursor: default; }

/* A rule with its reason written through it, rather than a bare line leaving
   the reader to work out what the two halves are. */
.sec-or {
  display: flex; align-items: center; gap: .7rem;
  margin: 1rem 0 .4rem;
  font-size: .76rem; color: var(--ink-dim);
}
.sec-or::before, .sec-or::after { content: ''; flex: 1; height: 1px; background: var(--line); }

.sec-say { display: block; margin-top: .5rem; font-size: .8rem; color: var(--ink-dim); }
.sec-say.bad { color: var(--bad); }

.sec-recovery { margin-top: 1rem; border-top: 1px solid var(--line); padding-top: .8rem; }
.sec-recovery summary { cursor: pointer; font-size: .82rem; color: var(--ink-dim); }
.sec-recovery summary:hover { color: var(--accent); }

.sec-add {
  display: flex; align-items: flex-end; gap: .6rem; flex-wrap: wrap;
  margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line);
}
.sec-add label { margin-bottom: 0; }
.sec-add label span { display: block; margin-bottom: .25rem; }
.sec-add input { width: 14rem; max-width: 100%; }
.sec-add .sec-say { flex: 1 1 100%; margin-top: .2rem; }

.sec-totp { display: flex; gap: 1.4rem; flex-wrap: wrap; align-items: flex-start; margin-top: 1rem; }
/* A white mat of its own: the QR is painted black on white whatever the
   console theme is, because a phone camera reads contrast and a dark-on-dark
   code is one nothing can scan. */
.sec-totp-qr { flex: 0 0 auto; background: #fff; padding: .7rem; border-radius: 10px; line-height: 0; }
.sec-totp-body { flex: 1 1 18rem; min-width: 0; }

.sec-secret {
  display: inline-block;
  margin: .2rem 0 1rem;
  padding: .45rem .7rem;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--panel-2);
  font-family: var(--mono); font-size: .88rem; letter-spacing: .04em;
  word-break: break-all;
}

.sec-confirm { display: flex; align-items: flex-end; gap: .6rem; flex-wrap: wrap; }
.sec-confirm label { width: 100%; margin-bottom: 0; }
.sec-confirm label span { display: block; margin-bottom: .25rem; }
.sec-confirm input {
  width: 7.5rem;
  font-family: var(--mono); font-size: 1.05rem; letter-spacing: .18em; text-align: center;
}

/* Transcribed by hand, and the difference between a zero and an O matters at
   exactly that moment. */
.sec-codes {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: .5rem; margin: 1rem 0 0; padding: 0; list-style: none;
}
.sec-codes li {
  padding: .55rem .7rem;
  border: 1px solid var(--line); border-radius: 6px; background: var(--panel-2);
  font-family: var(--mono); font-size: .95rem; letter-spacing: .06em;
  text-align: center; user-select: all;
}


/* ══════════════════════════════════════════════════════════════
   Security: is anything happening
   ══════════════════════════════════════════════════════════════ */

/* ── The band at the top ──
   Drawn only when something is raised, and drawn loudly. It is the reason the
   page exists, and it sits above the roster because a list of shops that are
   fine is a list nobody reads to the bottom of. */
.step.attack { border-color: var(--warn); }

.atk {
  display: flex;
  align-items: center;
  gap: .8rem;
  padding: .6rem .75rem;
  margin-bottom: .45rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--warn);
  border-radius: 8px;
  background: var(--panel-2);
}
.atk:hover { border-color: var(--accent); border-left-color: var(--warn); }
.atk-spike, .atk-sustained { border-left-color: var(--bad); }

/* The word, not a coloured dot. An operator reading this at speed should not
   have to remember which shade means which. */
.atk-state {
  flex: 0 0 auto;
  min-width: 5.6rem;
  padding: .16rem .45rem;
  border-radius: 4px;
  background: var(--warn);
  color: var(--bg);
  font-family: var(--mono);
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  text-align: center;
}
.atk-spike .atk-state,
.atk-sustained .atk-state { background: var(--bad); }

/* The same tag, inline beside a shop's name further down the page. */
.atk-state.small {
  min-width: 0;
  margin-left: .4rem;
  font-size: .58rem;
  vertical-align: .1em;
}
.atk-state.small.spike,
.atk-state.small.sustained { background: var(--bad); }
.atk-state.small.elevated  { background: var(--warn); }

.atk-who { flex: 1 1 auto; min-width: 0; font-size: .86rem; }
.atk-why { display: block; margin-top: .1rem; font-size: .76rem; color: var(--ink-dim); }
.atk-go  { flex: 0 0 auto; color: var(--ink-dim); }
.atk:hover .atk-go { color: var(--accent); }

/* A quieter way back to the roster, now that a shop's name leads to its
   security record instead. */
.sq-elsewhere { margin-left: .5rem; font-size: .7rem; }

/* ══════════════════════════════════════════════════════════════
   One shop, up close
   ══════════════════════════════════════════════════════════════ */

/* ── The chart ──
   Divs, not a charting library. There is one chart on this console and this is
   it; a dependency for it would be a dependency to keep in step forever, and
   a stacked bar per day is four rules. */
.bars {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 130px;
  padding: .6rem .2rem 0;
  border-bottom: 1px solid var(--line);
}
.bar { flex: 1 1 0; min-width: 0; height: 100%; display: flex; align-items: flex-end; }
/* A day with nothing still occupies its slot: the gaps are the shape. */
.bar-stack { width: 100%; display: flex; flex-direction: column-reverse; }
.bar:hover .bar-stack { outline: 1px solid var(--accent); outline-offset: 1px; }
.bar-seg { width: 100%; min-height: 1px; }
.bar-seg.high   { background: var(--bad); }
.bar-seg.medium { background: var(--warn); }
.bar-seg.low    { background: var(--accent); opacity: .75; }
.bar-seg.info   { background: var(--ink-dim); opacity: .45; }

.bars-key {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  margin-top: .55rem;
  font-size: .72rem; color: var(--ink-dim);
}
.bars-key i.sev { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: .3rem; }
.bars-key i.sev.high   { background: var(--bad); }
.bars-key i.sev.medium { background: var(--warn); }
.bars-key i.sev.low    { background: var(--accent); opacity: .75; }
.bars-key i.sev.info   { background: var(--ink-dim); opacity: .45; }
.bars-peak { margin-left: auto; font-family: var(--mono); }

/* ── Severity, as a word ── */
.sev-tag {
  display: inline-block;
  padding: .1rem .4rem;
  border-radius: 4px;
  font-family: var(--mono);
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.sev-tag.high   { background: var(--bad);  color: var(--bg); }
.sev-tag.medium { background: var(--warn); color: var(--bg); }
.sev-tag.low    { background: var(--panel-2); color: var(--ink-dim); border: 1px solid var(--line); }
.sev-tag.info   { background: var(--panel-2); color: var(--ink-dim); border: 1px solid var(--line); }

/* ── Which way it is going ──
   The number decides whether to do anything today; the total only says what
   this shop sees. */
.trend { font-family: var(--mono); font-size: .76rem; white-space: nowrap; }
.trend.up   { color: var(--bad); }
.trend.down { color: var(--ok); }
.trend.flat { color: var(--ink-dim); }

@media (max-width: 700px) {
  .bars { height: 96px; }
  .atk { flex-wrap: wrap; }
  .atk-state { min-width: 0; }
  .atk-who { flex: 1 1 100%; }
}


/* ── Two records that disagree ──────────────────────────────────
   The marketplace says a shop is published; the shop's own settings say it has
   never been told, or the other way round. Marked on the row rather than
   summarised at the top, because the fix is per shop and the button is right
   there. */
.q tr.drift td { background: rgba(224, 163, 62, .07); }
.q tr.drift td:first-child { box-shadow: inset 3px 0 0 var(--warn); }
.drift-no { color: var(--warn); }

/* -- Configuration ---------------------------------------------
   Forty-odd settings, each with a value, a source, a fallback, a default and
   sometimes a message, is too much for a six-column table: the text wraps
   into a wall. One card per setting instead, name and purpose on the left,
   the facts on the right, and a filter bar so the page can show only what
   needs attention. */
.cfg-toolbar { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin: .2rem 0 1.2rem; }
.cfg-toolbar .cfg-filter { display: flex; gap: .3rem; flex-wrap: wrap; }
.cfg-toolbar button { font: inherit; font-size: .78rem; padding: .3rem .7rem; border-radius: 999px; border: 1px solid var(--line); background: var(--panel); color: var(--ink-dim); cursor: pointer; }
.cfg-toolbar button[aria-pressed=true] { color: var(--accent); border-color: var(--accent); background: var(--accent-dim); }
.cfg-toolbar input[type=search] { min-width: 14rem; font-size: .82rem; }
.cfg-toolbar .cfg-count { font-size: .78rem; color: var(--ink-dim); margin-left: auto; }
.cfg-list { display: grid; gap: .5rem; }
.cfg-item {
  display: grid; grid-template-columns: minmax(16rem, 22rem) 1fr; gap: .4rem 1.4rem;
  padding: .75rem .95rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
}
.cfg-item.is-bad  { box-shadow: inset 3px 0 0 var(--bad); }
.cfg-item.is-warn { box-shadow: inset 3px 0 0 var(--warn); }
.cfg-item.is-db   { border-color: var(--accent); }
.cfg-item[hidden] { display: none; }
.cfg-id code { font-size: .8rem; word-break: break-all; }
.cfg-chips { display: inline-flex; gap: .25rem; flex-wrap: wrap; margin-left: .3rem; vertical-align: middle; }
.cfg-chip { font-size: .66rem; text-transform: uppercase; letter-spacing: .04em; padding: .08rem .4rem; border-radius: 4px; border: 1px solid var(--line-soft); color: var(--ink-dim); background: var(--panel-2); }
.cfg-chip.secret { color: var(--warn); }
.cfg-chip.runtime { color: var(--accent); }
.cfg-purpose { margin-top: .3rem; font-size: .78rem; line-height: 1.4; color: var(--ink-dim); }
.cfg-body { min-width: 0; }
.cfg-value { display: flex; gap: .55rem; align-items: baseline; flex-wrap: wrap; }
.cfg-value .val { font-family: var(--mono); font-size: .8rem; word-break: break-word; }
.cfg-msg { margin-top: .35rem; font-size: .8rem; line-height: 1.4; padding-left: .7rem; border-left: 2px solid var(--line); }
.cfg-msg.error   { border-color: var(--bad);  }
.cfg-msg.warning { border-color: var(--warn); }
.cfg-msg.info    { color: var(--ink-dim); }
.cfg-msg b { font-weight: 600; text-transform: capitalize; }
.cfg-msg.error b   { color: var(--bad); }
.cfg-msg.warning b { color: var(--warn); }
.cfg-facts { display: flex; gap: .3rem 1.2rem; flex-wrap: wrap; margin-top: .5rem; font-size: .74rem; color: var(--ink-dim); }
.cfg-facts span b { font-weight: 600; color: var(--ink-dim); margin-right: .3rem; }
.cfg-facts span code { font-size: .72rem; color: var(--ink); word-break: break-all; }
.cfg-edit { margin-top: .5rem; }
.cfg-edit summary { cursor: pointer; font-size: .78rem; color: var(--accent); width: max-content; }
.cfg-form { margin-top: .5rem; display: grid; gap: .45rem; max-width: 34rem; }
.cfg-form label { display: grid; gap: .15rem; font-size: .76rem; color: var(--ink-dim); }
.cfg-form input[type=text], .cfg-form input[type=number], .cfg-form select { width: 100%; font-size: .82rem; }
.cfg-actions { display: flex; gap: .4rem; flex-wrap: wrap; }
.cfg-history { margin-top: 1.4rem; }
.cfg-history summary { cursor: pointer; font-size: .95rem; font-weight: 700; }
.cfg-history table { width: 100%; border-collapse: collapse; margin-top: .6rem; font-size: .8rem; }
.cfg-history th { text-align: left; font-weight: 600; color: var(--ink-dim); font-size: .72rem; padding: .3rem .6rem .3rem 0; border-bottom: 1px solid var(--line); }
.cfg-history td { padding: .4rem .6rem .4rem 0; border-bottom: 1px solid var(--line-soft); vertical-align: top; word-break: break-word; }
.cfg-history form { margin: 0; }
.cfg-empty { padding: 1rem; border: 1px dashed var(--line); border-radius: var(--radius); color: var(--ink-dim); font-size: .82rem; }
@media (max-width: 860px) {
  .cfg-item { grid-template-columns: 1fr; gap: .3rem; }
  .cfg-toolbar .cfg-count { margin-left: 0; width: 100%; }
}

/* ── Infrastructure ──
   What the host publishes about itself. Rows rather than tiles: every value
   here is a label and a number with the host's own grade beside it, and a
   column of those reads faster than a grid of boxes. Nothing new is invented
   -- the pills, the table and the panel are the ones the rest of the console
   uses. */
.infra-head {
  display: flex; align-items: center; justify-content: space-between; gap: .8rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .8rem 1rem; margin-top: .6rem;
}
.infra-head-main { min-width: 0; overflow-wrap: anywhere; }
.infra-summary { margin: .5rem 0 0; font-size: .82rem; color: var(--ink-dim); }
.infra-rows { margin-top: .6rem; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.infra-row {
  display: grid; grid-template-columns: 12rem minmax(8rem, 1fr) auto; gap: .6rem;
  align-items: baseline; padding: .5rem .8rem; border-bottom: 1px solid var(--line-soft);
  font-size: .84rem;
}
.infra-row:last-child { border-bottom: 0; }
.infra-label { color: var(--ink-dim); }
.infra-value { font-family: var(--mono); overflow-wrap: anywhere; }
.infra-extra { text-align: right; font-size: .78rem; }
.infra-dim   { color: var(--ink-dim); }
.infra-absent {
  margin-top: .6rem; padding: .8rem 1rem; border: 1px dashed var(--line);
  border-radius: var(--radius); color: var(--ink-dim); font-size: .84rem;
}
.infra-services code { overflow-wrap: anywhere; }
.infra-boundary { max-width: 52rem; }

/* the dashboard's one card */
.infra-card {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .8rem 1rem; margin-top: .6rem; display: grid; gap: .35rem;
}
.infra-card-head { display: flex; align-items: center; gap: .6rem; }
.infra-card-head b { margin-right: auto; overflow-wrap: anywhere; }
.infra-card-bits { font-size: .82rem; font-family: var(--mono); color: var(--ink); overflow-wrap: anywhere; }
.infra-card-backup { font-size: .8rem; display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.infra-card-age { font-size: .76rem; }
.infra-card-link { font-size: .8rem; justify-self: start; }

/* Phones: the three-column row becomes two lines, and the grade sits under
   the value rather than being squeezed against it. */
@media (max-width: 40rem) {
  .infra-row { grid-template-columns: 1fr; gap: .15rem; padding: .6rem .7rem; }
  .infra-extra { text-align: left; }
  .infra-head { flex-wrap: wrap; }
}


/* ── How wide things are allowed to get ──
   Nothing here was capped, so on a 2560px monitor a three-column table was
   2300px wide and a sentence ran the whole width of the glass. The rule is
   not one width: it is a width per KIND of content.

   - the page itself stops at 120rem, which is about 1920px. Past that the
     rail-to-content ratio stops making sense and the eye loses the row it
     was following. Below it, nothing changes at all.
   - prose stops at 46rem, the length a line stays readable at.
   - forms stop at 60rem, so a row of fields does not become a row of twelve.
   - dense diagnostic tables are NOT capped: they earn their width, and the
     page cap already holds them.
   Everything is max-width, so every existing narrow behaviour is untouched. */
.content { max-width: 120rem; }

/* Prose: explanations, empty states, footnotes. */
.step .note,
.note,
.platform-note,
.cfg-purpose,
.empty p,
.infra-summary { max-width: 46rem; }

/* Forms and the panels that are mostly form. */
.op-new,
.cfg-form { max-width: 60rem; }

/* The dashboard's own cards: a summary card has no business being 1500px
   wide. They sit in a grid that fills, rather than stretching to fit. */
.ops-grid {
  display: grid; gap: .8rem; margin-top: .6rem;
  grid-template-columns: repeat(auto-fill, minmax(21rem, 30rem));
  align-items: start;
}
.ops-grid > * { margin-top: 0; }

/* Dense tables keep their width but stop growing columns apart past the
   point where a row can still be read across. */
.sec-table, table.q { max-width: 100%; }

/* One dead rule and one duplicate, found while measuring the above:
   h2.sec was declared twice (the first never applied), and .msg-layout
   collapsed at 1100px and again at 700px. Removed rather than re-styled. */

/* A status that is fine, said quietly: the eye should find the amber row. */
.infra-ok { color: var(--ink-dim); font-family: var(--mono); font-size: .76rem; letter-spacing: .04em; }
.infra-services td { vertical-align: baseline; }
/* Sections need air between them on a page this dense. */
.infra-rows + .sec, .infra-services + .sec, .sec-table + .sec { margin-top: 2.2rem; }


/* ── Applications and deployments ──
   Rows rather than cards: an application is a line of facts, and a column of
   lines is what an operator scans. The row only draws attention to itself
   when something is wrong, so a healthy platform reads as quiet. */
.app-list { margin-top: .6rem; display: grid; gap: .5rem; }
.app-row {
  background: var(--panel); border: 1px solid var(--line); border-left: 2px solid var(--line);
  border-radius: var(--radius); padding: .75rem .95rem;
}
.app-row-attention { border-left-color: var(--warn); }
.app-head { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem; flex-wrap: wrap; }
.app-id { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; min-width: 0; }
.app-id b { font-size: .95rem; }
.app-kind {
  font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-dim);
  border: 1px solid var(--line-soft); border-radius: 4px; padding: .05rem .35rem; background: var(--panel-2);
}
.app-tenant { font-family: var(--mono); font-size: .72rem; }
.app-badges { display: flex; align-items: center; gap: .45rem; white-space: nowrap; }
.app-env {
  font-family: var(--mono); font-size: .68rem; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-dim);
}
.app-env-production { color: var(--accent); }
.app-facts {
  margin-top: .55rem; display: grid; gap: .3rem .9rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); font-size: .8rem;
}
.app-facts > div { display: flex; align-items: baseline; gap: .45rem; min-width: 0; }
.app-label { color: var(--ink-dim); font-size: .68rem; text-transform: uppercase; letter-spacing: .06em; min-width: 5.4rem; }
.app-hash { font-family: var(--mono); font-size: .76rem; word-break: break-all; }
.app-reasons { margin: .5rem 0 0; padding-left: 1.1rem; font-size: .78rem; color: var(--warn); }
.app-releases { margin-top: .5rem; }
.app-releases summary { cursor: pointer; font-size: .76rem; color: var(--ink-dim); }
.app-release-list { margin-top: .4rem; display: grid; gap: .2rem; }
.app-release { font-size: .78rem; }
.app-current { font-size: .66rem; text-transform: uppercase; letter-spacing: .06em; color: var(--accent); }
.app-inv { margin-top: .8rem; padding: .6rem .8rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); }
.app-inv-head { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.app-deploys td { vertical-align: top; }
.app-deploys .app-action { word-break: break-word; }

/* Phones: the fact grid becomes one column and the badges wrap under the
   name, rather than any column being hidden -- a fact you cannot see is a
   fact you will not check. */
@media (max-width: 46rem) {
  .app-facts { grid-template-columns: 1fr; }
  .app-label { min-width: 4.6rem; }
  .app-deploys { font-size: .78rem; }
}


/* ── Domains ──
   The colour rule this page lives by: an unknown is grey, never amber. A
   missing inventory field is a gap in what we have written down, not a fault
   in the system, and drawing the two the same way would teach the reader to
   ignore the colour that matters.

   The page reads in falling order of urgency, and the type follows it: the
   domain and what it still needs from Bluehost; then, inset, each name this
   host serves with what was observed of it; then, quieter, what a person
   recorded about the accounts behind it; evidence stays folded until asked
   for. A domain this host serves nothing under is drawn quieter still -- not
   broken, just nothing to observe here. */

/* the page's figures, one line */
.dom-summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .45rem 1.3rem;
  margin: .1rem 0 .9rem; padding: .55rem .9rem;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
  font-size: .8rem; color: var(--ink-dim);
}
.dom-sum-group { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem 1rem; }
.dom-sum-group + .dom-sum-group { padding-left: 1.3rem; border-left: 1px solid var(--line-soft); }
.dom-sum-item b { font-family: var(--mono); font-size: .95rem; font-weight: 700; color: var(--ink); margin-right: .1rem; }
.dom-sum-item.is-ok b   { color: var(--ok); }
.dom-sum-item.is-warn b { color: var(--warn); }
.dom-sum-item.is-bad b  { color: var(--bad); }

/* the two kinds of fact on this page, said once, side by side */
.dom-intro {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 21rem), 1fr)); gap: .8rem 1.8rem;
  margin-bottom: 1rem; padding: .65rem .95rem;
  border: 1px solid var(--line-soft); border-radius: var(--radius);
  font-size: .8rem; line-height: 1.55; color: var(--ink-dim);
}
.dom-intro p { margin: .25rem 0 0; max-width: 64ch; }
.dom-intro b { color: var(--ink); font-weight: 600; }
.dom-layer {
  font-family: var(--mono); font-size: .63rem; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-dim);
}

/* one domain */
.dom-list { display: grid; gap: .8rem; }
.dom-card {
  min-width: 0; padding: .95rem 1.1rem 1rem;
  background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--line);
  border-radius: var(--radius);
}
.dom-card.dep-depends { border-left-color: var(--warn); border-left-color: color-mix(in srgb, var(--warn) 60%, var(--line)); }
.dom-card.dep-clear   { border-left-color: var(--ok);   border-left-color: color-mix(in srgb, var(--ok) 60%, var(--line)); }
.dom-card.is-quiet { background: transparent; padding-top: .75rem; padding-bottom: .8rem; }
.dom-card.is-quiet .dom-inv { margin-top: .5rem; padding-top: .55rem; }
.dom-card-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: .45rem 1rem; }
.dom-title { flex: 1 1 16rem; min-width: 0; }
.dom-name {
  margin: 0; font-family: var(--mono); font-size: 1.06rem; font-weight: 700; line-height: 1.3;
  color: var(--ink); overflow-wrap: anywhere;
}
.dom-card.is-quiet .dom-name { font-size: .98rem; }
.dom-purpose { margin: .2rem 0 0; font-size: .8rem; line-height: 1.45; color: var(--ink-dim); }
.dom-badges { flex: none; max-width: 100%; }
.dom-dep { display: inline-block; font-size: .72rem; padding: .22rem .62rem; }
.dom-dep .dom-unknown { font-size: inherit; }
.dom-unknown { color: var(--ink-dim); font-style: italic; font-size: .78rem; }
.dom-nohost { margin: .5rem 0 0; font-size: .76rem; font-style: italic; color: var(--ink-dim); }

/* the names this host serves under it, each its own row, inset */
.dom-hosts {
  margin-top: .8rem; min-width: 0;
  background: var(--panel-2); border: 1px solid var(--line-soft);
  border-radius: calc(var(--radius) - 2px);
}
.dom-host { padding: .5rem .8rem .4rem; min-width: 0; }
.dom-host + .dom-host { border-top: 1px solid var(--line); }
.dom-host-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .6rem; }
.dom-hostname { font-family: var(--mono); font-size: .86rem; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; min-width: 0; }
.dom-host-meta { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: .15rem .45rem; min-width: 0; }
.dom-app { font-size: .76rem; color: var(--ink-dim); }
.dom-mode { font-family: var(--mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-dim); }
.dom-mode-prod { color: var(--accent); }
.dom-hoststate {
  margin-left: auto; font-family: var(--mono); font-size: .64rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--ink-dim);
}
.dom-hoststate.is-ok   { color: var(--ok); }
.dom-hoststate.is-warn { color: var(--warn); }
.dom-hoststate.is-bad  { color: var(--bad); }
.dom-slugs { margin: .3rem 0 .1rem 1rem; display: grid; gap: .15rem; }
.dom-slug { font-size: .74rem; display: flex; flex-wrap: wrap; gap: .4rem; }
.dom-slug code { font-family: var(--mono); }

/* what was observed of one name: a grid of label over state */
.domobs { margin-top: .4rem; min-width: 0; }
/* label beside state, each pair at least one common width so the states line
   up from one name to the next: one line per name on a wide screen, wrapping
   whole pairs (never a word inside a pill) on a narrow one */
.domobs-strip { display: flex; flex-wrap: wrap; gap: .35rem 1rem; }
.domobs-item { flex: none; min-width: 9.6rem; display: flex; align-items: center; gap: .45rem; }
.domobs-what { flex: none; font-family: var(--mono); font-size: .62rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-dim); }
.domobs-item .pill { font-size: .76rem; padding: .2rem .62rem; white-space: nowrap; }

/* the evidence behind it: folded, but plainly a control */
.domobs-detail { margin-top: .25rem; }
.domobs-detail > summary {
  display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: .15rem .5rem;
  max-width: 100%; padding: .05rem .35rem .05rem .1rem; border-radius: 4px;
  cursor: pointer; list-style: none; font-size: .75rem; color: var(--ink-dim);
}
.domobs-detail > summary::-webkit-details-marker { display: none; }
.domobs-detail > summary::before { content: '▸'; color: var(--accent); font-size: .72rem; display: inline-block; width: .7rem; }
.domobs-detail[open] > summary::before { content: '▾'; }
.domobs-summary-word { color: var(--ink); font-weight: 600; }
.domobs-detail > summary:hover .domobs-summary-word { color: var(--accent); }
.domobs-detail > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.domobs-reasons { overflow-wrap: anywhere; }
.domobs-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); gap: .7rem 1.2rem;
  margin-top: .45rem; padding: .7rem .8rem; font-size: .76rem;
  background: var(--panel); border: 1px solid var(--line-soft); border-radius: 6px;
}
.domobs-block { min-width: 0; display: grid; align-content: start; gap: .2rem; }
.domobs-block > b { font-size: .74rem; margin-bottom: .1rem; }
.domobs-block > div { display: grid; grid-template-columns: 5.6rem minmax(0, 1fr); column-gap: .6rem; align-items: baseline; min-width: 0; }
.domobs-block > div > .app-label { min-width: 0; }
.domobs-block > div > span:last-child { overflow-wrap: anywhere; min-width: 0; }
.domobs-note { margin-top: .4rem; }

/* what a person recorded: its own, quieter layer */
.dom-inv {
  margin-top: .8rem; padding-top: .65rem; border-top: 1px solid var(--line-soft);
  display: grid; grid-template-columns: 7.2rem minmax(0, 1fr); column-gap: 1rem;
}
.dom-inv > .dom-layer { padding-top: .12rem; }
.dom-inv > :not(.dom-layer) { grid-column: 2; }
.dom-facts { margin: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .55rem 1.1rem; }
.dom-facts > div { min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .5rem; }
.dom-facts dt { font-size: .64rem; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-dim); }
.dom-facts dd { margin: 0; font-size: .84rem; color: var(--ink); overflow-wrap: anywhere; min-width: 0; }
.dom-facts dd .dom-unknown { font-size: .8rem; }
.dom-meta { margin: .55rem 0 0; max-width: 84ch; font-size: .74rem; line-height: 1.55; color: var(--ink-dim); }
.dom-source { display: inline; }
.dom-source > summary {
  display: inline; cursor: pointer; list-style: none; margin-left: .3rem; padding: 0 .3rem;
  border: 1px solid var(--line-soft); border-radius: 4px; font-size: .68rem; color: var(--ink-dim);
}
.dom-source > summary::-webkit-details-marker { display: none; }
.dom-source > summary::after { content: " ▸"; }
.dom-source[open] > summary::after { content: " ▾"; }
.dom-source > summary:hover { color: var(--accent); border-color: var(--accent); }
.dom-source > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dom-source[open] > span { display: block; margin-top: .3rem; padding-left: .6rem; border-left: 1px solid var(--line-soft); }
.dom-notes {
  margin: .55rem 0 0; max-width: 84ch; padding: .45rem .7rem;
  font-size: .78rem; line-height: 1.55; color: var(--ink-dim);
  border-left: 2px solid var(--line); background: var(--panel-2); border-radius: 0 4px 4px 0;
}
.dom-note-tag {
  margin-right: .55rem; font-family: var(--mono); font-size: .6rem; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-dim);
}
.dom-orphans { margin-top: .6rem; }

/* Leaving Bluehost: every domain on one line, state first */
.dom-leave { margin-top: .6rem; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); }
.dom-leave-row {
  display: grid; grid-template-columns: minmax(9rem, 13rem) 11.5rem minmax(0, 1fr);
  align-items: baseline; gap: .25rem 1rem; padding: .42rem .9rem; font-size: .8rem;
}
.dom-leave-row + .dom-leave-row { border-top: 1px solid var(--line-soft); }
.dom-leave-name { font-family: var(--mono); font-weight: 600; color: var(--ink); overflow-wrap: anywhere; min-width: 0; }
.dom-leave-state { justify-self: start; font-size: .7rem; padding: .12rem .55rem; white-space: nowrap; }
.dom-leave-detail { color: var(--ink-dim); line-height: 1.5; min-width: 0; }

@media (max-width: 62rem) {
  .dom-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dom-inv { grid-template-columns: minmax(0, 1fr); }
  .dom-inv > :not(.dom-layer) { grid-column: 1; }
  .dom-inv > .dom-facts { margin-top: .4rem; }
}
@media (max-width: 46rem) {
  .dom-card { padding: .85rem .85rem .9rem; }
  .dom-sum-group + .dom-sum-group { padding-left: 0; border-left: 0; }
  .dom-summary { gap: .35rem 1rem; }
  .dom-leave-row { grid-template-columns: minmax(0, 1fr) auto; }
  .dom-leave-detail { grid-column: 1 / -1; }
  .dom-slugs { margin-left: .5rem; }
}
@media (max-width: 26rem) {
  .dom-facts { grid-template-columns: minmax(0, 1fr); }
  .domobs-strip { gap: .35rem .75rem; }
  .domobs-item { min-width: 0; flex: 0 1 auto; }
  .dom-hostname { font-size: .78rem; }
  .dom-name { font-size: .98rem; }
}

/* ── Backups ──
   A ladder, read downwards. Each rung carries the question it answers, so a
   layer nothing can answer reads as an open question rather than a blank. */
.bak-verdict {
  margin-top: .6rem; padding: .7rem .95rem; border: 1px solid var(--line);
  border-left: 3px solid var(--line); border-radius: var(--radius); background: var(--panel);
  font-size: .84rem; display: grid; gap: .25rem;
}
.bak-verdict.bak-warning, .bak-layer.bak-warning { border-left-color: var(--warn); }
.bak-verdict.bak-error,   .bak-layer.bak-error   { border-left-color: var(--bad); }
.bak-verdict.bak-healthy, .bak-layer.bak-healthy { border-left-color: var(--ok); }
.bak-list { margin-top: .7rem; display: grid; gap: .45rem; }
.bak-layer {
  background: var(--panel); border: 1px solid var(--line); border-left: 2px solid var(--line);
  border-radius: var(--radius); padding: .7rem .95rem;
}
.bak-head { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem; flex-wrap: wrap; }
.bak-id { display: flex; align-items: baseline; gap: .55rem; min-width: 0; }
.bak-n {
  font-family: var(--mono); font-size: .7rem; color: var(--ink-dim);
  border: 1px solid var(--line-soft); border-radius: 50%; width: 1.3rem; height: 1.3rem;
  display: inline-flex; align-items: center; justify-content: center; flex: none;
}
.bak-q { margin-top: .25rem; font-size: .8rem; color: var(--ink-dim); }
.bak-summary { margin-top: .35rem; font-size: .82rem; overflow-wrap: anywhere; }
.bak-src { margin-top: .35rem; font-size: .76rem; }
.bak-src code { font-family: var(--mono); font-size: .74rem; }
.bak-missing {
  margin-top: .4rem; font-size: .78rem; color: var(--ink-dim);
  border-top: 1px solid var(--line-soft); padding-top: .35rem;
}
.bak-unknown { color: var(--ink-dim); font-style: italic; font-size: .78rem; }
.bak-gaps { margin: .5rem 0 0; padding-left: 1.1rem; font-size: .82rem; display: grid; gap: .3rem; }


/* Backups: the detail under a rung.
   Deliberately smaller and quieter than the rung itself -- the ladder is the
   verdict and this is only evidence, so it must never out-shout the thing it
   supports. Unknown values stay grey, never amber. */
.bak-detail { margin-top: .45rem; display: grid; gap: .18rem; }
.bak-detail-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: .8rem;
  font-size: .78rem; border-bottom: 1px solid var(--line-soft); padding-bottom: .16rem;
}
.bak-detail-row:last-child { border-bottom: 0; }
.bak-detail-label { color: var(--ink-dim); font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; }
.bak-detail-value { font-family: var(--mono); font-size: .78rem; text-align: right; }
.bak-proved, .bak-notproved { margin-top: .5rem; font-size: .78rem; }
.bak-proved ul, .bak-notproved ul { margin: .2rem 0 0; padding-left: 1.1rem; display: grid; gap: .12rem; }
.bak-notproved { color: var(--ink-dim); }

/* Reachability: the outside-in view.
   Same restraint as the rest of Ops -- a row per endpoint, quiet when healthy,
   and grey (never amber) when simply not observed. */
.reach-verdict {
  margin-top: .6rem; padding: .7rem .95rem; border: 1px solid var(--line);
  border-left: 3px solid var(--line); border-radius: var(--radius); background: var(--panel);
  font-size: .84rem; display: grid; gap: .25rem;
}
.reach-verdict.reach-healthy { border-left-color: var(--ok); }
.reach-verdict.reach-warning { border-left-color: var(--warn); }
.reach-verdict.reach-error   { border-left-color: var(--bad); }
.reach-counts { font-size: .92rem; }
.reach-caveat {
  margin-top: .6rem; padding: .6rem .8rem; border: 1px solid var(--line);
  border-left: 3px solid var(--warn); border-radius: var(--radius);
  background: var(--panel); font-size: .8rem;
}
.reach-list { margin-top: .7rem; display: grid; gap: .45rem; }
.reach-row {
  background: var(--panel); border: 1px solid var(--line); border-left: 2px solid var(--line);
  border-radius: var(--radius); padding: .65rem .95rem;
}
.reach-row-attention { border-left-color: var(--warn); }
.reach-head { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem; flex-wrap: wrap; }
.reach-id { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; min-width: 0; }
.reach-kind {
  font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-dim);
  border: 1px solid var(--line-soft); border-radius: 4px; padding: .05rem .35rem; background: var(--panel-2);
}
.reach-url { margin-top: .2rem; font-family: var(--mono); font-size: .76rem; overflow-wrap: anywhere; }
.reach-bits { margin-top: .25rem; font-size: .76rem; color: var(--ink-dim); font-family: var(--mono); }
.reach-note { margin-top: .3rem; font-size: .78rem; }
.reach-warn { color: var(--warn); }
.reach-unknown { color: var(--ink-dim); font-style: italic; font-size: .78rem; }
/* -- Shared operator primitives --------------------------------
   These are intentionally small composition rules, extracted from the
   Mission Control, facts and diagnostic pages rather than a component
   framework. */
.status-banner {
  display: flex; align-items: flex-start; gap: .45rem .8rem; flex-wrap: wrap;
  margin-bottom: .8rem; padding: .7rem .85rem;
  border: 1px solid var(--line); border-left-width: 3px;
  border-radius: var(--radius); background: var(--panel);
  font-size: .84rem;
}
.status-banner strong { color: var(--ink); }
.status-banner > span { color: var(--ink-dim); }
.status-details { flex: 1 0 100%; }
.status-banner.ok   { border-left-color: var(--ok); }
.status-banner.info { border-left-color: var(--accent); }
.status-banner.warn { border-left-color: var(--warn); }
.status-banner.bad  { border-left-color: var(--bad); }

.metric-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: .6rem; margin-bottom: 1rem;
}
.metric-card {
  min-width: 0; padding: .75rem .85rem;
  border: 1px solid var(--line); border-top: 2px solid var(--line);
  border-radius: var(--radius); background: var(--panel);
}
.metric-card.ok   { border-top-color: var(--ok); }
.metric-card.info { border-top-color: var(--accent); }
.metric-card.warn { border-top-color: var(--warn); }
.metric-card.bad  { border-top-color: var(--bad); }
.metric-value { font: 700 1.25rem/1.2 var(--mono); font-variant-numeric: tabular-nums; }
.metric-label { margin-top: .15rem; font-size: .76rem; font-weight: 600; }
.metric-meta { margin-top: .15rem; font-size: .7rem; color: var(--ink-dim); overflow-wrap: anywhere; }

.section-panel,
.diagnostic {
  margin-top: .8rem; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--panel); overflow: hidden;
}
.section-head,
.diagnostic-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: .75rem; padding: .75rem .85rem; border-bottom: 1px solid var(--line);
}
.section-head h2,
.diagnostic-head h2 { font-size: .9rem; }
.section-head p,
.diagnostic-head p { margin-top: .15rem; font-size: .76rem; color: var(--ink-dim); }
.section-body { padding: .8rem .85rem; }
.section-body > .table-scroll { margin: -.8rem -.85rem; }
.section-body > .table-scroll .q { border: 0; border-radius: 0; }
.evidence-stack { display: grid; gap: .5rem; }
.evidence-card { padding: .65rem .75rem; border: 1px solid var(--line-soft); border-radius: 6px; background: var(--bg); }
.evidence-card-head { display: flex; justify-content: space-between; align-items: flex-start; gap: .7rem; flex-wrap: wrap; }
.long-value { overflow-wrap: anywhere; word-break: break-word; }
.q-wide { min-width: 48rem; }

.action-toolbar {
  display: flex; align-items: center; gap: .45rem; flex-wrap: wrap;
  margin: .7rem 0 1rem; padding: .65rem .75rem;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
}
.action-toolbar-label { margin-right: .2rem; font-size: .7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-dim); }
.action-toolbar form { margin: 0; }
.plan-form { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; }
.store-danger-action { margin-top: .65rem; padding-top: .6rem; border-top: 1px solid var(--line-soft); }
.store-danger-action form { margin: 0; }
.platform-status { margin-top: .35rem; display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; font-size: .7rem; color: var(--ink-dim); }
.act-btn.danger { color: var(--bad); border-color: var(--bad); }
.act-btn.danger:hover:not(:disabled) { color: var(--bad); border-color: var(--bad); background: rgba(228,99,92,.08); }
.danger-zone {
  margin-top: 1.2rem; padding: .85rem;
  border: 1px solid var(--bad); border-radius: var(--radius); background: rgba(228,99,92,.05);
}
.danger-zone h2 { font-size: .9rem; color: var(--bad); }
.danger-zone p { margin: .25rem 0 .7rem; max-width: 54rem; font-size: .78rem; color: var(--ink-dim); }

.diagnostic-head .note { margin: .15rem 0 0; max-width: 58rem; }
.code-block {
  margin: 0; padding: .8rem .9rem; max-width: 100%; max-height: 32rem;
  overflow: auto; white-space: pre; font-size: .74rem; line-height: 1.55;
  color: var(--ink); background: var(--bg);
}
.diagnostic-actions { display: flex; gap: .5rem; padding: .65rem .85rem; border-top: 1px solid var(--line); }

@media (max-width: 700px) {
  .metric-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .section-head, .diagnostic-head { flex-direction: column; }
  .action-toolbar { align-items: stretch; }
  .action-toolbar-label { width: 100%; }
  .action-toolbar form, .action-toolbar .act-btn { flex: 1 1 auto; }
  .code-block { font-size: .7rem; }
}

@media (max-width: 430px) {
  .metric-grid { grid-template-columns: 1fr; }
  .row-actions .deploy-form,
  .row-actions .act-btn,
  .danger-zone form,
  .danger-zone .act-btn { width: 100%; }
}

/* ══════════════════════════════════════════════════════════════
   Mission Control at fleet scale (index.php, includes/fleet.php)

   A scan, not a wall of cards: four counts, the operator's queue, one
   compact row per shop. Healthy rows are quiet; weight goes to the ones
   that need somebody. Every status is a word, never colour alone.
   ══════════════════════════════════════════════════════════════ */
.visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.mc-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .6rem; margin: .4rem 0 .3rem; }
.mc-count { display: flex; align-items: baseline; gap: .45rem; padding: .6rem .8rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); color: var(--ink-dim); text-decoration: none; }
.mc-count b { font-size: 1.45rem; color: var(--ink); font-weight: 700; }
.mc-count.warn b { color: var(--warn); }
.mc-count.bad { border-color: var(--bad); }
.mc-count.bad b { color: var(--bad); }
.mc-count:hover, .mc-count:focus-visible { border-color: var(--accent); }
.mc-lifecycle { display: flex; flex-wrap: wrap; gap: .3rem 1rem; font-size: .8rem; color: var(--ink-dim); margin: 0 0 1rem; }
.mc-lifecycle a { color: var(--ink-dim); }
.mc-quiet { color: var(--ink-dim); font-size: .82rem; margin: .3rem 0 .6rem; }
.mc-queue { list-style: none; margin: 0 0 .5rem; padding: 0; border: 1px solid var(--line); border-radius: var(--radius); }
.mc-queue li { display: grid; grid-template-columns: 5.5rem minmax(8rem, 14rem) minmax(0, 1fr); gap: .6rem; align-items: baseline; padding: .45rem .7rem; border-top: 1px solid var(--line); font-size: .84rem; }
.mc-queue li:first-child { border-top: 0; }
.mc-q-subject { font-weight: 600; overflow-wrap: anywhere; }
.mc-q-text { color: var(--ink-dim); overflow-wrap: anywhere; }
.mc-notes summary, .mc-actions summary { cursor: pointer; color: var(--ink-dim); font-size: .82rem; padding: .35rem 0; }
.mc-filter { display: flex; flex-wrap: wrap; gap: .5rem .8rem; align-items: end; margin-bottom: .4rem; }
.mc-filter label { margin: 0; display: flex; flex-direction: column; gap: .2rem; }
.mc-filter input, .mc-filter select { min-width: 12rem; }
.mc-table-wrap { position: relative; overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); }
.mc-table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.mc-table th, .mc-table td { text-align: left; padding: .42rem .65rem; border-top: 1px solid var(--line); vertical-align: top; }
.mc-table thead th { border-top: 0; font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-dim); font-weight: 600; white-space: nowrap; }
.mc-table tbody th { font-weight: 600; min-width: 11rem; }
.mc-host { display: block; font-family: var(--mono); font-size: .7rem; color: var(--ink-dim); font-weight: 400; overflow-wrap: anywhere; }
/* One line: the reason is a hint, the shop's page has the whole story. */
.mc-why { display: block; font-size: .74rem; color: var(--ink-dim); margin-top: .15rem; max-width: 26rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mc-row-down, .mc-row-attention { background: color-mix(in srgb, var(--bad) 7%, transparent); }
.mc-dim { color: var(--ink-dim); }
.mc-warn { color: var(--warn); font-weight: 600; }
.mc-bad { color: var(--bad); font-weight: 600; }
.mc-mono { font-family: var(--mono); font-size: .72rem; color: var(--ink-dim); }
.mc-pages { display: flex; align-items: center; gap: .8rem; margin: .7rem 0; font-size: .82rem; color: var(--ink-dim); }
.mc-actions { margin: .8rem 0 1.2rem; }
/* Small screens: the essentials (shop, lifecycle, health, readiness); the
   rest is one tap away on the shop's page. No horizontal page scroll. */
@media (max-width: 1100px) { .mc-opt2 { display: none; } .mc-why { max-width: 16rem; } }
@media (max-width: 760px) {
  .mc-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mc-opt { display: none; }
  .mc-queue li { grid-template-columns: 1fr; gap: .15rem; }
  .mc-filter input, .mc-filter select { min-width: 0; width: 100%; }
  .mc-filter label { flex: 1 1 100%; }
}

/* ── Where a queued deploy or update is (pass 4, phase 12) ──
   Under the button that asked: a word for the state, then the timing the
   worker's facts allow (assets/ops-status.js keeps both current), then one
   line of detail. Colour follows the state; the word says it too. */
.ops-status {
  margin: .45rem 0 0;
  font-size: .82rem;
  line-height: 1.45;
  color: var(--ink-dim);
  max-width: 34rem;
}
.ops-status .ops-label { color: var(--ink); font-weight: 600; }
.ops-status .ops-timing { font-variant-numeric: tabular-nums; }
.ops-status .ops-detail:empty,
.ops-status .ops-timing:empty { display: none; }
.ops-status[data-state="running"] .ops-label { color: var(--accent); }
.ops-status[data-state="completed"] .ops-label { color: var(--ok); }
.ops-status[data-state="needs_attention"] .ops-label { color: var(--bad); }
.ops-status .ops-detail { display: block; }

/* ── Blocking (pass 4, phase 14) ── */
.abuse-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: .7rem 1rem; align-items: end; }
.abuse-form label { display: flex; flex-direction: column; gap: .3rem; font-size: .82rem; color: var(--ink-dim); }
.abuse-form .wide { grid-column: 1 / -1; }
.abuse-form .confirm { flex-direction: row; align-items: center; gap: .5rem; color: var(--bad); font-weight: 600; }
.abuse-form input[type=text], .abuse-form select, .abuse-revoke input { min-height: 38px; }
.abuse-revoke { display: flex; gap: .4rem; }
.abuse-preview { max-height: 20rem; overflow: auto; font-family: var(--mono); font-size: .78rem; background: var(--panel-2, var(--panel)); padding: .6rem .8rem; border-radius: var(--radius); }

/* ── Growth (pass 4, phase 19; growth.php) ── */
.gr-tabs { display: flex; flex-wrap: wrap; gap: .3rem; margin: 0 0 1rem; }
.gr-tabs a, .gr-scen a { padding: .35rem .75rem; border: 1px solid var(--line); border-radius: 999px; font-size: .84rem; text-decoration: none; color: inherit; }
.gr-tabs a.on, .gr-scen a.on { background: var(--ink, #222); color: var(--panel, #fff); border-color: transparent; }
.gr-scen { display: flex; flex-wrap: wrap; gap: .3rem; align-items: center; }
.gr-basis { display: inline-block; font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: .05rem .45rem; border-radius: 999px; border: 1px solid currentColor; }
.gr-basis.measured { color: var(--ok, #1f7a4d); }
.gr-basis.assumption { color: var(--warn, #9a6a00); }
.gr-basis.unknown { color: var(--bad, #a12a1a); }
.gr-inline { display: flex; gap: .3rem; flex-wrap: wrap; align-items: center; }
.gr-inline input[type=number] { width: 7rem; }
.gr-lever li { margin: .2rem 0; }
.gr-cal { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: .6rem; }
.gr-month { border: 1px solid var(--line); border-radius: var(--radius); padding: .5rem .6rem; }
.gr-month h3 { margin: 0 0 .3rem; font-size: .9rem; }
.gr-season { margin: .1rem 0; font-size: .78rem; color: var(--ink-dim); }
.gr-camp { margin: .15rem 0; font-size: .8rem; font-weight: 600; }
.gr-camp.gr-idea, .gr-camp.gr-planned { opacity: .7; }
.gr-opp { border-top: 1px solid var(--line); padding: .7rem 0; }
.gr-due { border-left: 3px solid var(--warn, #9a6a00); padding: .35rem .7rem; margin: .5rem 0 .8rem; }
/* Incidents (pass 4, phase 21): the facts, then the timeline. */
.inc-facts { display: grid; grid-template-columns: max-content 1fr; gap: .3rem 1rem; margin: .5rem 0 0; }
.inc-facts dt { color: var(--ink-dim); font-size: .85rem; }
.inc-facts dd { margin: 0; overflow-wrap: anywhere; }
.inc-summary { margin: .8rem 0 0; overflow-wrap: anywhere; }
.inc-status { display: inline-block; font-size: .78rem; font-weight: 700; padding: .05rem .5rem; border-radius: 999px; border: 1px solid currentColor; }
.inc-status.inc-open { color: var(--bad, #a12a1a); }
.inc-status.inc-monitoring { color: var(--warn, #9a6a00); }
.inc-status.inc-resolved { color: var(--ok, #1f7a4d); }
.inc-timeline { list-style: none; margin: 0 0 1rem; padding: 0; border-left: 2px solid var(--line); }
.inc-timeline li { position: relative; padding: .35rem 0 .55rem 1rem; overflow-wrap: anywhere; }
.inc-timeline li::before { content: ''; position: absolute; left: -6px; top: .75rem; width: 10px; height: 10px; border-radius: 50%; background: var(--line); }
.inc-timeline li.inc-k-status::before { background: var(--warn, #9a6a00); }
.inc-timeline li.inc-k-cause::before { background: var(--bad, #a12a1a); }
.inc-timeline li.inc-k-action::before { background: var(--ok, #1f7a4d); }
.inc-timeline time { color: var(--ink-dim); font-size: .8rem; margin-right: .4rem; }
.inc-timeline small { color: var(--ink-dim); margin-left: .3rem; }
@media (max-width: 640px) { .inc-facts { grid-template-columns: 1fr; } .inc-facts dd { margin-bottom: .4rem; } }
.gr-due-row td:first-child { box-shadow: inset 3px 0 0 var(--warn, #9a6a00); }
.gr-inline input[type=url] { min-width: 12rem; }
.gr-opp blockquote { margin: .3rem 0; padding-left: .7rem; border-left: 3px solid var(--line); font-size: .9rem; }
.gr-opp-form { display: grid; gap: .4rem; }
.gr-opp-form textarea { width: 100%; font: inherit; }
.gr-opp.gr-dismissed, .gr-opp.gr-posted { opacity: .65; }
.gr-answer { white-space: pre-wrap; background: var(--panel-2, var(--panel)); padding: .7rem .9rem; border-radius: var(--radius); font-size: .86rem; }
.gr-mem { margin: .3rem 0; font-size: .9rem; }

/* Platform flags on the shop page (Phase 5): one small form per flag. */
.flag-form { display: flex; flex-wrap: wrap; gap: .35rem; align-items: center; margin: 0; }
.flag-form input[name="flag_reason"] { min-width: 10rem; flex: 1 1 10rem; }

.mc-table tr.on th, .mc-table tr.on td { background: var(--panel-2, rgba(128,128,128,.08)); }
.gr-lever { padding-left: 1.4rem; }
.gr-opp ul { padding-left: 1.2rem; margin: .3rem 0; }
