/* RebelText operator console.
 *
 * One stylesheet, no build step, no framework. The look follows rebeltexting.com:
 * a black ground, cream type, the brand red as the one loud colour, condensed
 * uppercase headings over monospace body text, square corners and hard offset
 * shadows instead of soft ones.
 *
 * Dark is the default because it is the site's look. Light swaps the black
 * ground for the site's cream and keeps the red, and it applies when the OS
 * asks for it or the operator picks it from the top bar (admin.js sets
 * data-theme on <html>). Only tokens change between the two; nothing below the
 * token blocks names a colour that assumes one ground or the other.
 *
 * The CSP is default-src 'self', so the site's web fonts cannot be pulled from
 * Google. The stacks name them first (they are used when installed, or once the
 * files are vendored under /static) and fall back to the nearest system faces. */

:root {
  color-scheme: dark;

  --bg: #000000;
  --surface: #0e0e0e;
  --surface-2: #141414;
  --input: #1b1b1a;
  --border: rgb(255 255 255 / 8%);
  --border-strong: rgb(234 229 205 / 16%);
  --text: #eae5cd;
  --muted: #888880;
  --faint: #525252;

  --primary: #ed462d;
  --primary-hover: #d83c1f;
  --primary-dark: #da3921;
  --rose: #f0b0a6;

  --accent: var(--primary);
  --accent-text: #000000;
  /* Type on a slab filled with --text: primary buttons, the console tag. */
  --on-text: #000000;
  --topbar: rgb(0 0 0 / 92%);
  /* One height for everything in the top bar, so the theme control and the
     account menu line up edge to edge. */
  --topbar-control: 36px;
  --sidebar-w: 232px;

  /* Which lockup file shows: white type on the dark ground, black on the
     light. A display value rather than a colour, since an <img> takes none. */
  --logo-on-dark: block;
  --logo-on-light: none;

  /* Status tints. Red is the brand, so "wrong" is carried by the rose text on a
     dark red wash rather than by the button red itself. */
  --danger: #ff6b55;
  --danger-bg: rgb(237 70 45 / 12%);
  --ok-bg: rgb(110 231 168 / 9%);
  --ok-text: #8fdcaa;
  --warn-bg: rgb(245 194 107 / 10%);
  --warn-text: #f0c674;
  --info-bg: rgb(234 229 205 / 7%);
  --info-text: #d8d3ba;

  --shadow: 6px 6px 0 0 #262626;
  --shadow-cream: 4px 4px 0 0 rgb(234 229 205 / 16%);
  --radius: 0;

  --font-heading: "Barlow Semi Condensed", "Avenir Next Condensed", "Roboto Condensed",
    "Arial Narrow", "Helvetica Neue", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
}

/* The light palette, twice: once for an OS that prefers light and has not been
   overridden to dark, once for an explicit choice of light. CSS has no way to
   share one block between a media query and an attribute selector. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;

    --bg: #f1eee0;
    --surface: #faf8f0;
    --surface-2: #ebe7d4;
    --input: #ffffff;
    --border: rgb(14 14 14 / 9%);
    --border-strong: rgb(14 14 14 / 18%);
    --text: #111110;
    --muted: #62605a;
    --faint: #a29f93;
    --rose: #b8321c;
    --on-text: #eae5cd;

    --danger: #c2321b;
    --danger-bg: rgb(237 70 45 / 10%);
    --ok-bg: rgb(22 120 70 / 9%);
    --ok-text: #1d6e42;
    --warn-bg: rgb(180 110 0 / 10%);
    --warn-text: #8a5300;
    --info-bg: rgb(14 14 14 / 5%);
    --info-text: #3b3a35;

    --shadow: 6px 6px 0 0 #d6d1b8;
    --topbar: rgb(241 238 224 / 92%);
    --logo-on-dark: none;
    --logo-on-light: block;
  }
}

:root[data-theme="light"] {
  color-scheme: light;

  --bg: #f1eee0;
  --surface: #faf8f0;
  --surface-2: #ebe7d4;
  --input: #ffffff;
  --border: rgb(14 14 14 / 9%);
  --border-strong: rgb(14 14 14 / 18%);
  --text: #111110;
  --muted: #62605a;
  --faint: #a29f93;
  --rose: #b8321c;
  --on-text: #eae5cd;

  --danger: #c2321b;
  --danger-bg: rgb(237 70 45 / 10%);
  --ok-bg: rgb(22 120 70 / 9%);
  --ok-text: #1d6e42;
  --warn-bg: rgb(180 110 0 / 10%);
  --warn-text: #8a5300;
  --info-bg: rgb(14 14 14 / 5%);
  --info-text: #3b3a35;

  --shadow: 6px 6px 0 0 #d6d1b8;
  --topbar: rgb(241 238 224 / 92%);
  --logo-on-dark: none;
  --logo-on-light: block;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 13px/1.6 var(--font-mono);
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--primary); color: #000; }

code {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted);
}

a { color: var(--text); }

/* ── Top bar ─────────────────────────────────────────────────────────────── */

/* ── Shell ───────────────────────────────────────────────────────────────── */

/* Sidebar rather than a top bar. The nav outgrew a single row: every section
   added to it squeezed the rest, and the page title had to compete with it for
   the same strip. Down the side the list has room to grow.

   A grid rather than a fixed-position aside, so the main column is laid out
   next to the sidebar instead of underneath it with a margin standing in. */
.shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "side top"
    "side main";
  min-height: 100vh;
  /* Without this the leftover space under a short page is shared out between
     the rows, which at narrow widths pushes the content halfway down the
     screen. Only relevant once the sidebar stacks above main. */
  align-content: start;
}

.shell > main { grid-area: main; }

.sidebar {
  grid-area: side;
  position: sticky;
  top: 0;
  /* On a phone this element is the drawer, a popover. At this width the
     browser's popover styles are undone: it is not hidden, not fixed to the
     viewport and not boxed. */
  right: auto;
  bottom: auto;
  left: auto;
  width: auto;
  margin: 0;
  border: 0;
  color: inherit;
  align-self: start;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 28px;
  height: 100vh;
  padding: 24px 16px;
  background: var(--topbar);
  border-right: 1px solid var(--border);
  overflow-y: auto;
}

/* Keyboard users would otherwise tab the whole nav on every page. */
.skip {
  position: absolute;
  left: -9999px;
  z-index: 20;
  padding: 10px 16px;
  background: var(--text);
  color: var(--on-text);
  font-family: var(--font-heading);
  text-transform: uppercase;
}
.skip:focus { left: 8px; top: 8px; }

/* The lockup centred over the sidebar with the "admin" tag beside it: the
   customer console carries the same lockup without one, so the tag is what
   tells the two apart. The lockup is a size down from the customer console's
   so the pair fits on one line inside the sidebar. */
.sidebar-head { display: flex; align-items: center; justify-content: center; }

/* What only a phone shows: the brand and the menu button in the top bar, and
   the drawer's own close button and account controls. */
.topbar-brand, .menu-toggle, .drawer-close, .drawer-account { display: none; }

.brand {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 6px;
  text-decoration: none;
}

.logo { width: 161px; height: 36px; }
.brand .logo { width: 143px; height: 32px; }
.logo.on-dark { display: var(--logo-on-dark); }
.logo.on-light { display: var(--logo-on-light); }

/* The tag's box stands on REBELTEXT's baseline and is as tall as its capitals:
   in the lockup's 42 units they run from 2.09 to 23.08, which on a 32px
   lockup is 1.6px to 17.6px. So the box starts 1.6px down and is 16px tall —
   a 12px line and 2px either side. The narrow layout's smaller lockup scales
   both. */
.brand span, .topbar-brand span {
  margin-top: 1.6px;
  line-height: 12px;
  padding: 2px 6px;
  font-family: var(--font-mono);
  text-transform: uppercase;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--on-text);
  background: var(--text);
}

/* The full width of the sidebar, out past its padding, so a row's tint runs
   edge to edge. The rows put the padding back as their own. */
.sidebar nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 -16px;
}

.sidebar nav a {
  padding: 8px 16px 8px 30px;
  color: var(--muted);
  text-decoration: none;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.sidebar nav a:hover { color: var(--text); background: var(--surface-2); }
/* The page being shown: the brand red washed across the row, and a red edge
   on it. That edge is the only red in the sidebar, so it is the one thing the
   eye finds. */
.sidebar nav a.active {
  color: var(--text);
  background: rgb(237 70 45 / 12%);
  box-shadow: inset 3px 0 0 var(--primary);
}

/* The Settings group: an item like the others that opens to show its pages,
   indented under it. The chevron says which way it is. */
.nav-group { display: flex; flex-direction: column; gap: 2px; }
.nav-group > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px 8px 30px;
  color: var(--muted);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  list-style: none;
}
.nav-group > summary::-webkit-details-marker { display: none; }
.nav-group > summary::after { content: "\25B8"; font-size: 11px; color: var(--faint); }
.nav-group[open] > summary::after { content: "\25BE"; }
.nav-group > summary:hover { color: var(--text); background: var(--surface-2); }
.nav-group > summary.current { color: var(--text); }
.nav-group > summary:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
/* Block-level by hand: what a <details> holds besides its summary is laid out
   as a run of text, not as items of the flex column it sits in, so the links
   would otherwise share lines. */
.sidebar nav .nav-group > a { display: block; padding-left: 44px; font-size: 14px; }

/* ── Top bar ─────────────────────────────────────────────────────────────── */

/* Sticky, and everything at the far end: the start of the bar is the page's
   own heading, just below it, and nothing up here should compete with that.
   The customer console's top bar, without the balance. */
.topbar {
  grid-area: top;
  position: sticky;
  top: 0;
  z-index: 9;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  min-height: 64px;
  padding: 12px 32px;
  background: var(--topbar);
  border-bottom: 1px solid var(--border);
  backdrop-filter: blur(8px);
}

/* Who is signed in, and the menu behind their name. */
.account-menu { position: relative; }

.account-menu summary {
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--topbar-control);
  max-width: 240px;
  padding: 0 12px;
  border: 1px solid var(--border-strong);
  color: var(--text);
  cursor: pointer;
  list-style: none;
}
.account-menu summary::-webkit-details-marker { display: none; }
.account-menu summary:hover,
.account-menu[open] summary { border-color: var(--text); }
.account-menu summary.active { border-bottom-color: var(--primary); }
.account-menu summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

.caret { color: var(--muted); font-size: 10px; }

.account-menu .menu {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 30;
  display: flex;
  flex-direction: column;
  min-width: 240px;
  max-width: calc(100vw - 32px);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow);
}

.menu-who {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
}

.account-menu .menu > a {
  padding: 10px 14px;
  color: var(--text);
  text-decoration: none;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-left: 2px solid transparent;
}
.account-menu .menu > a:hover { background: var(--surface-2); }
.account-menu .menu > a[aria-current="page"] { border-left-color: var(--primary); }

.signout {
  margin: 0;
  padding: 12px 14px;
  border-top: 1px solid var(--border);
}

.profile-name {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.who {
  color: var(--muted);
  font-size: 12px;
  overflow-wrap: anywhere;
}

/* A phone: the top bar across the top, holding the brand, the theme and the
   menu button, and the sidebar as a drawer from the right that the button
   opens. The drawer is a popover, so the browser opens and closes it and
   dismisses it on Escape or a tap outside; no script is needed, which matters
   because the CSP forbids it inline. The account controls move into the
   drawer's foot. */
@media (max-width: 860px) {
  .shell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "top"
      "main";
  }

  .sidebar:not(:popover-open) { display: none; }
  .sidebar {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(300px, 86vw);
    height: 100dvh;
    gap: 24px;
    padding: 16px;
    background: var(--surface);
    border-right: 0;
    border-left: 1px solid var(--border-strong);
    transform: translateX(100%);
    transition: transform 0.2s ease, overlay 0.2s allow-discrete, display 0.2s allow-discrete;
  }
  .sidebar:popover-open { transform: none; }
  @starting-style {
    .sidebar:popover-open { transform: translateX(100%); }
  }
  .sidebar::backdrop { background: rgb(0 0 0 / 55%); }

  .sidebar-head { justify-content: space-between; }
  .menu-toggle, .drawer-close { display: inline-flex; }

  .drawer-account {
    display: flex;
    flex-direction: column;
    margin: auto -16px -16px;
    border-top: 1px solid var(--border);
  }
  .drawer-account .menu-who { padding: 14px 16px 10px; border-bottom: 0; }
  .drawer-account > a {
    padding: 10px 16px;
    color: var(--text);
    text-decoration: none;
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 14px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
  }
  .drawer-account > a:hover { background: var(--surface-2); }
  .drawer-account > a.active { background: rgb(237 70 45 / 12%); box-shadow: inset 3px 0 0 var(--primary); }
  .drawer-account .signout { padding: 12px 16px 16px; }

  .topbar {
    gap: 8px;
    min-height: 0;
    padding: 12px 16px;
    box-shadow: inset 0 3px 0 var(--primary);
  }
  .topbar-brand { display: flex; align-items: flex-start; gap: 8px; margin-right: auto; text-decoration: none; }
  .topbar-brand .logo { width: 125px; height: 28px; }
  /* On a 28px lockup the capitals run from 1.4px to 15.4px: a 14px box. */
  .topbar-brand span { margin-top: 1.4px; line-height: 10px; }
  .account-menu { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
}

/* The theme toggle cycles auto → light → dark: a square holding the glyph
   alone, the height of the account menu beside it. It is rendered hidden and
   admin.js reveals it, so without script there is no button that does
   nothing. */
.topbar .theme-toggle, .menu-toggle, .drawer-close {
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--topbar-control);
  height: var(--topbar-control);
  padding: 0;
  font-size: 16px;
  line-height: 1;
}
.topbar .theme-toggle { display: inline-flex; }
.topbar .theme-toggle[hidden] { display: none; }
.drawer-close { font-size: 14px; }

/* ── Layout ──────────────────────────────────────────────────────────────── */

main {
  /* width rather than the auto a block would normally take. As a grid item an
     auto width resolves against the content, so one table wider than the phone
     stretches the whole column and drags the page sideways with it. Resolving
     against the track instead keeps main the width of the screen and leaves
     the overflow to the table's own scroller. */
  width: 100%;
  min-width: 0;
  max-width: 1160px;
  margin: 0 auto;
  padding: 40px 32px 80px;
}

/* The signed-out shell, the same as the customer console's: no card and no
   chrome, a narrow column on the page's own ground with the brand red glowing
   in from the top. */
body:has(main.centered) {
  background:
    radial-gradient(900px 420px at 50% -120px, rgb(237 70 45 / 22%), transparent 70%),
    var(--bg);
}

main.centered {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  max-width: 408px;
  padding: 48px 24px;
}

h1, h2, h3 {
  font-family: var(--font-heading);
  text-transform: uppercase;
  line-height: 1.05;
  color: var(--text);
}

h1 { font-size: 40px; font-weight: 800; letter-spacing: -0.005em; margin: 0 0 24px; }
h2 { font-size: 20px; font-weight: 700; letter-spacing: 0.02em; margin: 0 0 18px; }

/* A heading that follows a block opens a new group; it does not caption the one
   above it. With no room over it, it sat the same distance from the card it
   followed as from the card it introduced, and belonged to neither. Only the
   page-level flow is scoped, so a heading that opens a card or a dialog keeps
   its own margin. */
main > * + h2,
main > * + .section-head,
section > * + h2,
section > * + .section-head { margin-top: 40px; }

main > * + h3,
section > * + h3 { margin-top: 30px; }

/* A muted aside inside a heading is a qualifier, not part of the title. */
h1 .muted, h2 .muted {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.05em;
  text-transform: none;
}

section { margin-bottom: 44px; }

.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 14px;
}

.section-head h1, .section-head h2 { margin: 0; }
.section-head h1 + .pill { margin-right: auto; }
.section-head > .muted { font-size: 12px; }

.more {
  color: var(--primary);
  text-decoration: none;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.more:hover { color: var(--text); }

.muted { color: var(--muted); }
.right { text-align: right; }
.negative { color: var(--danger); }

.empty {
  padding: 36px;
  text-align: center;
  color: var(--muted);
  background: var(--surface);
  border: 1px dashed var(--border-strong);
}

/* ── Stats ───────────────────────────────────────────────────────────────── */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 0;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow);
}

/* Tiles share borders like the site's feature grid rather than floating as
   separate cards. */
.stat {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 20px 22px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  margin: 0 -1px -1px 0;
}

.stat:first-child { box-shadow: inset 3px 0 0 var(--primary); }

.stat-value {
  font-family: var(--font-heading);
  font-size: 34px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.stat-label {
  order: -1;
  color: var(--muted);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ── Tables ──────────────────────────────────────────────────────────────── */

table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--border-strong);
}

th, td {
  padding: 12px 16px;
  text-align: left;
  border-bottom: 1px solid var(--border);
}

th {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--bg);
  border-bottom-color: var(--border-strong);
}

tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--surface-2); }

td.right, th.right { text-align: right; font-variant-numeric: tabular-nums; }

.name { display: block; font-weight: 500; color: var(--text); }

/* ── Pills ───────────────────────────────────────────────────────────────── */

/* The site's small tag: square, mono, uppercase, tracked out. */
.pill {
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid currentcolor;
  font-size: 10px;
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.pill-active { background: var(--ok-bg); color: var(--ok-text); }
.pill-suspended { background: var(--warn-bg); color: var(--warn-text); }
.pill-closed { background: var(--danger-bg); color: var(--danger); }
/* An account sending without a subscription. Warn-coloured rather than ok- or
   danger-: nothing is wrong, but it is not the normal state and should catch
   the eye of anyone scanning the page. */
.pill-exempt { background: var(--warn-bg); color: var(--warn-text); }

/* Operator roles. Superadmin is marked out and the ordinary role is not: the
   list is read to find who holds the privileged one, and colouring both would
   make neither stand out. */
.pill-superadmin { background: var(--info-bg); color: var(--info-text); }
.pill-operator { background: transparent; color: var(--muted); }

/* ── Forms ───────────────────────────────────────────────────────────────── */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 20px;
  align-items: center;
}

input, select, button {
  font: inherit;
  padding: 9px 12px;
  border: 1px solid var(--border-strong);
  border-radius: 0;
  background: var(--input);
  color: var(--text);
}

/* A native select sizes itself, ignoring the padding and line-height that set
   an input's height, so the two never line up. Without native appearance it
   takes the same box as an input. The arrow is two gradient triangles: the
   CSP allows no data: image to draw it with. */
select {
  appearance: none;
  padding-right: 32px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 17px) 55%, calc(100% - 12px) 55%;
  background-size: 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}
select:hover { background-image:
    linear-gradient(45deg, transparent 50%, var(--text) 50%),
    linear-gradient(135deg, var(--text) 50%, transparent 50%); }

input::placeholder, textarea::placeholder { color: var(--faint); }

input:hover, select:hover, textarea:hover { border-color: rgb(234 229 205 / 30%); }

input[type="search"] { flex: 1; min-width: 0; }
/* A filter row with several fields: each stays wide enough to read a number or
   an ID, so the row wraps instead of squeezing them all onto one line. */
.filters input[type="search"] { min-width: 14ch; }
input[type="checkbox"] { accent-color: var(--primary); }

/* A password box with the show/hide eye admin.js adds inside its right edge. */
.pw-field { position: relative; display: block; }
.pw-field > input { width: 100%; padding-right: 42px; }

/* Edge draws its own reveal eye; two would be one too many. */
.pw-field > input::-ms-reveal { display: none; }

.pw-field > .pw-toggle {
  position: absolute;
  /* A card's button rule aligns it to flex-start, which an absolute box
     obeys too and so stops stretching between top and bottom. */
  align-self: stretch;
  top: 1px;
  right: 1px;
  bottom: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  color: var(--muted);
  box-shadow: none;
  transform: none;
}

.pw-field > .pw-toggle:hover { color: var(--text); box-shadow: none; transform: none; }
.pw-field > .pw-toggle:focus-visible { outline-offset: -2px; }

input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
}

/* A select marked data-searchable, as admin.js turns it: a text box dressed as
   the select it replaces — same arrow — over a list that narrows as you type.
   The list floats over whatever is below rather than pushing it down. */
.combo {
  position: relative;
  display: block;
  min-width: 0;
}

.combo > .combo-input {
  width: 100%;
  margin: 0;
  padding-right: 32px;
  text-overflow: ellipsis;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 17px) 55%, calc(100% - 12px) 55%;
  background-size: 5px 5px;
  background-repeat: no-repeat;
}

.combo-list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-width: min(420px, calc(100vw - 32px));
  max-height: 280px;
  overflow-y: auto;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow);
}

.combo-list > li {
  padding: 7px 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}

.combo-list > li[hidden] { display: none; }
.combo-list > li[aria-selected="true"] { color: var(--primary); }
.combo-list > li.active { background: var(--info-bg); color: var(--text); }
.combo-list > li.combo-empty { color: var(--muted); cursor: default; }
/* The filter row is not a grid, so give the box room for a key's name. */
.filters .combo { width: 26ch; max-width: 100%; }

button:focus-visible, a:focus-visible, summary:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* The site's call-to-action: a solid slab with a hard offset shadow that the
   button presses into on hover. Primary buttons are cream so the brand red stays
   free to mean "this one is destructive". */
button {
  background: var(--text);
  color: var(--on-text);
  border-color: transparent;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 4px 4px 0 0 var(--primary);
  transition: transform 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease;
}

button:hover { transform: translate(2px, 2px); box-shadow: 2px 2px 0 0 var(--primary); }
button:active { transform: translate(4px, 4px); box-shadow: none; }

.signout button { width: 100%; }

.signout button, .theme-toggle, .menu-toggle, .drawer-close {
  background: transparent;
  color: var(--muted);
  border-color: var(--border-strong);
  font-weight: 500;
  font-size: 11px;
  padding: 6px 12px;
  box-shadow: none;
}

.signout button:hover, .theme-toggle:hover, .menu-toggle:hover, .drawer-close:hover { color: var(--text); border-color: var(--text); transform: none; }

.clear {
  color: var(--muted);
  text-decoration: none;
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0 8px;
}

.clear:hover { color: var(--text); }

/* ── Login ───────────────────────────────────────────────────────────────── */

.card {
  padding: 28px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow);
}

/* The lockup above the heading. Signed out there is no sidebar, so without it
   the page never says whose console it is. */
.login-brand { display: flex; justify-content: center; margin: 0 0 36px; }
.login-brand .logo { width: 176px; height: auto; }

/* The card's box steps aside, which lets the lockup, the heading and the line
   under it be ordered ahead of the page-level error the layout prints first:
   an error reads under the heading, beside the form, rather than above the
   logo. */
main.centered > .login { display: contents; }
main.centered .login-brand { order: -3; }
main.centered .login h1 { order: -2; }
main.centered .intro { order: -1; }

.login h1 { margin: 0 0 18px; font-size: 30px; letter-spacing: 0.01em; text-align: center; }
.login .intro { margin: -8px 0 18px; font-size: 12px; text-align: center; }

.login form { display: flex; flex-direction: column; gap: 6px; }

/* The two-factor code, a box to a digit across the column's width. */
.otp { display: flex; gap: 8px; }
.otp input {
  flex: 1 1 0;
  min-width: 0;
  height: 54px;
  padding: 0;
  text-align: center;
  font-size: 22px;
  font-weight: 600;
}

.login label, .card label {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 10px;
}

/* The one action: the column's width, a taller box to press, and the brand red
   rather than the console's cream slab. */
.login button {
  align-self: stretch;
  width: 100%;
  margin-top: 22px;
  padding: 15px;
  font-size: 13px;
  letter-spacing: 0.14em;
  background: var(--primary);
  color: #000;
  box-shadow: none;
}
.login button:hover { background: var(--primary-hover); box-shadow: none; transform: none; }

/* Abandoning a half-finished login is a way out, not the thing to do, so the
   second form on the two-factor card is quiet: same shape, no fill, no shadow. */
.login-secondary { margin-top: 18px; }

.login-secondary button {
  margin-top: 0;
  padding: 8px;
  font-size: 11px;
  font-weight: 500;
  background: transparent;
  color: var(--muted);
  border-color: var(--border-strong);
  box-shadow: none;
}

.login-secondary button:hover {
  color: var(--text);
  border-color: var(--text);
  transform: none;
}

/* The enrolment code is always black on white, in both themes. The dark theme
   would otherwise render it inverted, which a good half of readers refuse, and
   a QR is not a decorative element that should follow the page. The white
   ground is painted by a rect inside the SVG rather than by this rule, so the
   quiet zone survives a stylesheet that failed to load. */
.totp-qr {
  display: block;
  width: 232px;
  height: auto;
  max-width: 100%;
  margin: 0 0 16px;
  border: 1px solid var(--border-strong);
}

/* The enrolment secret is meant to be read aloud off the screen and typed into
   a phone, so it wraps at any width rather than scrolling sideways, and it gets
   room around it — a mistyped character costs a failed code and a retry. */
.totp-secret { margin: 0 0 14px; }

.totp-secret code {
  display: block;
  padding: 10px 12px;
  font-size: 13px;
  line-height: 1.7;
  letter-spacing: 0.08em;
  overflow-wrap: anywhere;
}

.error, .flash-ok {
  padding: 12px 16px;
  margin: 0 0 20px;
  border: 1px solid currentcolor;
  border-left-width: 4px;
  font-size: 12px;
}

.error { background: var(--danger-bg); color: var(--danger); }
.flash-ok { background: var(--ok-bg); color: var(--ok-text); }


/* ── Pager ───────────────────────────────────────────────────────────────── */

.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  margin-top: 20px;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.pager a { color: var(--text); text-decoration: none; border-bottom: 1px solid var(--primary); }
.pager a:hover { color: var(--primary); }
.pager .disabled { color: var(--faint); }

/* ── Detail pages ────────────────────────────────────────────────────────── */

.crumbs {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.crumbs span { color: var(--faint); }
.crumbs a { color: var(--primary); text-decoration: none; }
.crumbs a:hover { color: var(--text); }

.subtitle { margin: -14px 0 28px; max-width: 760px; }
.section-head + .subtitle { margin-top: 6px; }

.small { font-size: 11px; }
.warn { color: var(--warn-text); }
.wrap { word-break: break-all; }

/* A message's text in a log table: wide enough to read a line of it, no wider
   than a column should be, and cut after three lines so one long message does
   not make its row a page tall. Line breaks the sender typed are kept. */
.msg-body { min-width: 14rem; max-width: 28rem; overflow-wrap: anywhere; }
.msg-body .clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  white-space: pre-line;
}
.msg-body .attach { display: block; }

td a { color: var(--text); text-decoration: none; }
td a .name, td a code { border-bottom: 1px solid transparent; }
td a:hover .name, td a:hover code, td a:hover { color: var(--primary); }

p a, .notfound a { color: var(--primary); text-decoration: none; }
p a:hover, .notfound a:hover { color: var(--text); }

/* A second table directly under another reads as one continuous section. */
table.stacked { margin-top: 14px; }

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

.chip {
  padding: 8px 14px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}

.chip strong {
  margin-right: 4px;
  font-family: var(--font-heading);
  font-size: 18px;
  font-weight: 800;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Key/value table for a single record, rather than the column layout used for
   lists. */
table.kv th {
  width: 200px;
  background: transparent;
  vertical-align: top;
  border-bottom-color: var(--border);
  padding-top: 14px;
}

table.kv tbody tr:hover { background: transparent; }

pre.body {
  margin: 0;
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--primary);
  white-space: pre-wrap;
  word-break: break-word;
  font-family: var(--font-mono);
  font-size: 13px;
}

/* MMS attachments on the message page. Numbered because order is part of the
   payload — the carrier receives them in this sequence. */
ol.media {
  margin: 0;
  padding-left: 22px;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.7;
}

ol.media li { word-break: break-all; }

.notfound { text-align: center; }
.notfound h1 { font-size: 72px; }

/* ── Status pills ────────────────────────────────────────────────────────── */

/* Message lifecycle. Grouped by what an operator needs to notice: settled and
   fine, still moving, or wrong. */
.pill-msg-delivered, .pill-msg-sent { background: var(--ok-bg); color: var(--ok-text); }
.pill-msg-accepted, .pill-msg-queued, .pill-msg-sending,
.pill-msg-scheduled { background: var(--info-bg); color: var(--info-text); }
.pill-msg-failed, .pill-msg-undelivered,
.pill-msg-rejected { background: var(--danger-bg); color: var(--danger); }
.pill-msg-canceled { background: transparent; color: var(--muted); }

/* Webhook delivery. */
.pill-pending, .pill-sending { background: var(--info-bg); color: var(--info-text); }
.pill-delivered { background: var(--ok-bg); color: var(--ok-text); }
.pill-failed, .pill-dead { background: var(--danger-bg); color: var(--danger); }
.pill-disabled { background: var(--warn-bg); color: var(--warn-text); }

/* 10DLC registration. Approved is the only state that carries traffic. */
.pill-reg-approved { background: var(--ok-bg); color: var(--ok-text); }
.pill-reg-draft { background: transparent; color: var(--muted); }
.pill-reg-submitted, .pill-reg-pending { background: var(--info-bg); color: var(--info-text); }
.pill-reg-failed, .pill-reg-expired,
.pill-reg-suspended { background: var(--danger-bg); color: var(--danger); }

.pill-ledger { background: transparent; color: var(--muted); }
.pill-released, .pill-quarantined { background: var(--warn-bg); color: var(--warn-text); }

/* Volt webhook log. Applied is the only outcome that changed a message; quiet
   outcomes are routine, amber ones deserve a look, red ones were refused or
   broke. */
.pill-volt-applied { background: var(--ok-bg); color: var(--ok-text); }
.pill-volt-ignored, .pill-volt-not_receipt { background: transparent; color: var(--muted); }
.pill-volt-foreign_message { background: var(--info-bg); color: var(--info-text); }
.pill-volt-unknown_message, .pill-volt-unparseable { background: var(--warn-bg); color: var(--warn-text); }
.pill-volt-rejected, .pill-volt-error,
.pill-volt-bad_request { background: var(--danger-bg); color: var(--danger); }

/* Customer webhook attempts: 2xx is the only success, as in the failed-only
   filter. */
.pill-attempt-ok { background: var(--ok-bg); color: var(--ok-text); }
.pill-attempt-failed { background: var(--danger-bg); color: var(--danger); }

/* Webhook payloads and responses open in a dialog from their table row, so a
   long body never stretches the row or pushes the table sideways. */
.log-modal {
  width: min(960px, calc(100vw - 32px));
  max-height: 85vh;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow);
}
.log-modal[open] { display: flex; flex-direction: column; }
.log-modal::backdrop { background: rgb(0 0 0 / 60%); }
body:has(.log-modal[open]) { overflow: hidden; }

.log-modal-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}
.log-modal-head h2 {
  flex: 1;
  margin: 0;
  font-size: 14px;
  overflow-wrap: anywhere;
}

.log-modal-body { padding: 4px 16px 16px; overflow: auto; }
.log-modal-body pre {
  white-space: pre-wrap;
  word-break: break-all;
  margin: 8px 0 0;
  padding: 12px;
  font-size: 12px;
  background: var(--surface-2);
}
.log-modal-body h3 { margin: 16px 0 0; font-size: 12px; }

/* The facts at the top of a webhook's details. */
.log-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 16px;
  margin: 12px 0 0;
  font-size: 12px;
  text-align: left;
}
.log-facts dt { color: var(--muted); }
.log-facts dd { margin: 0; overflow-wrap: anywhere; }

/* A page's own facts, as label/value pairs. Wider and plainer than log-facts,
   which is sized for a modal. */
.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 10px 24px;
  margin: 0;
  align-items: baseline;
}
.facts dt { color: var(--muted); font-size: 13px; }
.facts dd { margin: 0; overflow-wrap: anywhere; }

@media (max-width: 560px) {
  .facts { grid-template-columns: 1fr; gap: 2px; }
  .facts dd { margin-bottom: 10px; }
}

.log-attempts { margin-top: 8px; font-size: 12px; }
.log-attempts .current td { background: var(--surface-2); font-weight: 700; }

/* ── Actions ─────────────────────────────────────────────────────────────── */

.actions { display: flex; flex-direction: column; gap: 16px; }

.action {
  padding: 20px 22px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
}

.action h3 { margin: 0 0 6px; font-size: 18px; font-weight: 700; letter-spacing: 0.02em; }
.action .small { margin: 0 0 14px; }

.action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin: 0 0 10px;
}

.action-row:last-child { margin-bottom: 0; }
.action-row input[type="text"], .action-row input[type="password"],
.action-row .pw-field { flex: 1; min-width: 200px; }
.action-row input[type="number"] { width: 140px; }

/* A row whose inputs are not self-explanatory gets a caption over each one.
   Bottom-aligned so the uncaptioned inputs and the button sit on the same line
   as the captioned ones. */
.action-row.fields { align-items: flex-end; }

.field { display: flex; flex-direction: column; gap: 6px; }

.field > span {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* A checkbox needs its label beside it rather than above, and needs to sit on
   the same baseline as the inputs it shares a row with. */
.check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}

.card label.check { font-size: 12px; letter-spacing: 0; text-transform: none; }

.check input { margin: 0; }

/* ── Editable rows ───────────────────────────────────────────────────────── */

/* Some tables are also forms: a brand's name and status are edited where they
   are read, rather than on a separate page that would show the same six fields
   with nothing else on it. A <form> cannot wrap table cells, so each row's form
   element sits in one cell and its inputs reach it by id — which means the
   controls must be sized down to look like table content rather than like a
   form dropped into a table. */
td input[type="text"], td select {
  width: 100%;
  min-width: 90px;
  padding: 5px 8px;
  font-size: 12px;
}
td select { padding-right: 28px; }

td input[type="text"] + code { display: block; margin-top: 4px; }
td .muted.small { display: block; }

/* An editable row ends in a cell holding a save button beside whatever else
   acts on that row. Those neighbours are usually forms, which are block boxes —
   so without a flex container here the button takes one line and each form
   takes another, and a one-line row stands up three lines tall. */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}

/* Save and Quarantine do very different things, and a 6px gap between a benign
   button and a destructive one is a misclick waiting to happen. */
.row-actions .danger-group { margin-left: 20px; }

/* A table carrying form controls has a floor its cells cannot compress past —
   a select has to show enough of a campaign name to pick by. Below that width
   the table overflows, and what falls off the right edge is the actions column:
   the operator loses the buttons rather than a column of digits they could
   guess at. Scrolling the table keeps them reachable. */
.table-scroll { overflow-x: auto; }

/* Form dialogs: the plan add, edit and archive forms. The head and foot stay
   put while the fields scroll, so Save and Close are always in reach. */
.form-modal {
  width: min(820px, calc(100vw - 32px));
  max-height: min(90vh, 900px);
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow);
}
.form-modal-small { width: min(480px, calc(100vw - 32px)); }
.form-modal::backdrop { background: rgb(0 0 0 / 60%); }
body:has(.form-modal[open]) { overflow: hidden; }
.form-modal[open] > form { display: flex; flex-direction: column; max-height: inherit; }
.form-modal-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border);
}
.form-modal-head h2 { flex: 1; margin: 0; font-size: 18px; overflow-wrap: anywhere; }
.form-modal-head h2 code { font-size: 12px; font-weight: 400; margin-left: 6px; }
.form-modal-body { padding: 16px 20px; overflow: auto; }
.form-modal-body > p:first-child { margin-top: 0; }
.form-modal-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  padding: 14px 20px;
  border-top: 1px solid var(--border);
}
.form-modal-foot input[type="text"] { flex: 1; min-width: 200px; }
/* Stacked, full-width fields for a short dialog form. */
.form-grid { display: grid; gap: 14px; }
.form-grid input, .form-grid select { width: 100%; box-sizing: border-box; }

/* A table row's actions side by side. Each label stays on one line; when the
   cell is narrow the buttons wrap instead. */
.row-buttons { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; align-items: center; }
.row-buttons .small-btn { white-space: nowrap; }

/* A row's actions in a popover menu, for rows with more than fit on a line.
   admin.js positions it under its button; without script the browser centres
   it, which still works. Items are plain text rows, not slabs. */
.row-menu {
  margin: 0;
  padding: 4px 0;
  min-width: 200px;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow);
}
button[popovertarget] { white-space: nowrap; }
.row-menu form { margin: 0; }
.row-menu button {
  display: block;
  width: 100%;
  padding: 8px 14px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 11px;
  text-align: left;
  white-space: nowrap;
  box-shadow: none;
}
.row-menu button:hover, .row-menu button:focus-visible {
  background: var(--surface-2);
  transform: none;
  box-shadow: none;
  outline-offset: -2px;
}
/* Repeats the reset: button.destructive comes later in this file and would
   otherwise paint the item as a red slab. */
.row-menu button.destructive { background: transparent; color: var(--danger); box-shadow: none; }
.row-menu button.destructive:hover { background: var(--danger-bg); box-shadow: none; }
.row-buttons form { margin: 0; }
/* The Stripe price sits under the list price it bills, trimmed: IDs are long
   and the full one is on hover and in the edit dialog. */
td .plan-stripe-id { max-width: 16ch; margin-left: auto; overflow: hidden; text-overflow: ellipsis; }

/* The prices a plan could be pointed at. One radio group with the amount box
   as its last option, so choosing a price and typing an amount are visibly the
   same decision rather than two controls that can disagree. */
.plan-price-list { margin: 0 0 10px; padding: 0; list-style: none; }
.plan-price-list li { padding: 2px 0; }
.plan-price-list .check { display: flex; align-items: baseline; gap: 8px; white-space: normal; }
.plan-price-list .plan-price-amount { font-weight: 700; }
.plan-price-list code.plan-stripe-id { max-width: 22ch; margin-left: 0; }
.plan-price-new { white-space: nowrap; }

.plan-description { flex: 1; min-width: 240px; }
.plan-description input { width: 100%; }

/* The plan form groups its fields, each with a sentence saying what it does:
   rate, burst and overage are not self-explanatory to anyone setting them. */
.plan-group {
  margin: 0 0 14px;
  padding: 12px 16px 14px;
  border: 1px solid var(--border-strong);
}
.plan-group legend {
  padding: 0 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text);
}
.plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
}
.plan-grid input { width: 100%; }
/* Amount, currency, interval and the Stripe price read as one line. */
.plan-price { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; margin-bottom: 8px; }
.plan-price input, .plan-price select { box-sizing: border-box; }
.plan-price input.plan-amount { width: 130px; }
.plan-price input.plan-currency { width: 70px; text-transform: uppercase; }
.plan-stripe { flex: 1; min-width: 200px; }
.plan-stripe input { width: 100%; }
.plan-overage-toggle { margin: 2px 0 12px; font-size: 13px; color: var(--text); white-space: normal; }
.field-hint { display: block; font-size: 11px; line-height: 1.5; color: var(--muted); }
.field .field-hint { max-width: 36ch; }

/* The catalogue: numbers and prices stay on one line with their unit beneath,
   and the plan column keeps enough width that names do not break mid-word. */
td.nowrap { white-space: nowrap; }
td.plan-name { min-width: 180px; }

/* A plan whose Stripe price charges something other than the advertised amount
   gets a row of its own directly under it, tinted rather than merely coloured:
   it has to survive being skimmed past in a table of twenty. */
tr.plan-mismatch td {
  padding-top: 6px;
  background: var(--warn-bg);
  border-top: 0;
  white-space: normal;
  line-height: 1.6;
}
tr.plan-mismatch .muted { display: block; max-width: 80ch; }

/* Destructive actions sit behind a disclosure. The CSP forbids inline script,
   so there is no confirm() to lean on — an explicit expand plus a mandatory
   reason is the friction instead, and it leaves a better audit row than a
   dialog would. */
details.danger summary {
  cursor: pointer;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 18px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--primary);
}

details.danger[open] summary { margin-bottom: 10px; }
details.danger {
  border-color: rgb(237 70 45 / 45%);
  background:
    repeating-linear-gradient(-45deg, rgb(237 70 45 / 4%) 0 10px, transparent 10px 20px),
    var(--surface);
}

button.destructive {
  background: var(--primary);
  color: #000;
  box-shadow: 4px 4px 0 0 var(--text);
}

button.destructive:hover { background: var(--primary-hover); box-shadow: 2px 2px 0 0 var(--text); }

button.small-btn, a.small-btn {
  padding: 5px 10px;
  font-size: 11px;
  box-shadow: 3px 3px 0 0 var(--primary);
}

button.small-btn:hover { box-shadow: 1px 1px 0 0 var(--primary); transform: translate(2px, 2px); }
button.destructive.small-btn { box-shadow: 3px 3px 0 0 var(--text); }
button.destructive.small-btn:hover { box-shadow: 1px 1px 0 0 var(--text); }

/* A link dressed as a button, for actions that navigate rather than post. */
a.small-btn {
  display: inline-block;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--on-text);
  background: var(--text);
}

a.small-btn:hover { color: var(--on-text); }

/* ── Tabs ────────────────────────────────────────────────────────────────── */

/* Each tab is a link to ?tab=, not a CSS or script toggle: the URL is the
   state, so a tab can be linked into a ticket and the back button works. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin: 0 0 32px;
  border-bottom: 1px solid var(--border-strong);
}

.tab {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  color: var(--muted);
  text-decoration: none;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-bottom: 3px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
}

.tab:hover { color: var(--text); }

.tab.active {
  color: var(--text);
  border-bottom-color: var(--primary);
}

.tab-count {
  padding: 0 6px;
  background: var(--input);
  border: 1px solid var(--border-strong);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

.tab.active .tab-count {
  background: var(--primary);
  border-color: transparent;
  color: #000;
}

/* The mutating tab is set apart from the read-only ones. */
.tab-danger { color: var(--primary); margin-left: auto; }
.tab-danger:hover { color: var(--rose); }
.tab-danger.active { color: var(--primary); }

@media (max-width: 700px) {
  main { padding: 28px 16px 64px; }
  h1 { font-size: 32px; }
  .tabs { flex-wrap: nowrap; overflow-x: auto; }
  .tab-danger { margin-left: 0; }
}

/* An action that needs one field alongside its button, inside a table cell. */
.inline-form { display: flex; gap: 8px; justify-content: flex-end; }
.inline-form input[type="text"] { width: 130px; padding: 5px 8px; font-size: 11px; }

/* ── Deliverability trace ────────────────────────────────────────────────── */

textarea {
  font: inherit;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border-strong);
  border-radius: 0;
  background: var(--input);
  color: var(--text);
  resize: vertical;
}

textarea.mono { font-family: var(--font-mono); font-size: 12px; }

.card h2 { margin-bottom: 6px; }
.card form { display: flex; flex-direction: column; gap: 6px; }
.card .small { margin: 2px 0 0; }
.card button { align-self: flex-start; margin-top: 22px; padding: 12px 22px; }

/* Volt webhooks. Event checkboxes wrap in one row under their legend, and each
   table row's delete stays a quiet disclosure rather than a card in a cell. */
.card fieldset { border: 0; padding: 0; margin: 10px 0 0; display: flex; flex-wrap: wrap; gap: 8px 18px; }
.card fieldset legend {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0;
  margin-bottom: 8px;
}
.card fieldset .check { margin-top: 0; }
.table-scroll + .card, .empty + .card { margin-top: 32px; }
.volt-form { margin-bottom: 28px; }

/* Customer webhook endpoints on the account page. The secret banner is the one
   place a credential is printed, so it is selectable in one click and scrolls
   rather than wrapping mid-token. */
.event-picker { border: 0; padding: 0; margin: 4px 0 12px; display: flex; flex-wrap: wrap; gap: 6px 16px; }
.event-picker legend { font-size: 13px; font-weight: 500; padding: 0; margin-bottom: 6px; }
.secret-once {
  padding: 12px 16px;
  margin: 0 0 20px;
  background: var(--ok-bg);
  color: var(--ok-text);
  border: 1px solid currentcolor;
  border-left-width: 4px;
}
.secret-once h2 { margin: 0 0 4px; font-size: 16px; color: inherit; }
.secret-once .secret {
  display: block;
  overflow-x: auto;
  white-space: nowrap;
  user-select: all;
  padding: 8px 10px;
  color: var(--text);
  background: var(--input);
  border: 1px solid var(--border-strong);
}

/* The live banner is the page saying it is still watching. Paired with the meta
   refresh, which is what actually reloads. */
.live {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  margin: 0 0 24px;
  background: var(--info-bg);
  color: var(--info-text);
  border: 1px solid var(--border-strong);
  border-left: 4px solid var(--primary);
  font-size: 12px;
}

.live-dot {
  width: 8px;
  height: 8px;
  background: var(--primary);
  flex: none;
  animation: pulse 1.4s ease-in-out infinite;
}

@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

/* Respect a reduced-motion preference: the dot is decoration, and the banner
   still reads correctly without it moving. The buttons' press is decoration
   too. */
@media (prefers-reduced-motion: reduce) {
  .live-dot { animation: none; }
  button, button:hover, button:active { transition: none; transform: none; }
}

/* ── Timeline ────────────────────────────────────────────────────────────── */

.timeline {
  list-style: none;
  margin: 0;
  padding: 22px 22px 4px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
}

.tl {
  position: relative;
  display: flex;
  gap: 16px;
  padding: 0 0 20px 0;
}

/* The connecting rail, drawn from each marker down to the next. The last item
   does not get one, or the timeline would trail off into nothing. */
.tl::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 16px;
  bottom: 0;
  width: 1px;
  background: var(--border-strong);
}

.tl:last-child::before { display: none; }

.tl-marker {
  position: relative;
  z-index: 1;
  flex: none;
  width: 11px;
  height: 11px;
  margin-top: 5px;
  background: var(--surface);
  border: 2px solid var(--muted);
}

.tl-ok .tl-marker { border-color: var(--ok-text); background: var(--ok-text); }
.tl-problem .tl-marker { border-color: var(--primary); background: var(--primary); }

.tl-pending .tl-marker { border-style: dashed; border-color: var(--faint); }
.tl-pending .tl-label { font-style: italic; }

.tl-body { flex: 1; min-width: 0; }

.tl-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
}

.tl-label {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.tl-stage {
  padding: 0 7px;
  background: var(--input);
  border: 1px solid var(--border-strong);
  color: var(--muted);
  font-size: 10px;
  letter-spacing: 0.05em;
}

.tl-at { font-size: 11px; margin-left: auto; }

.tl-detail {
  font-size: 12px;
  margin-top: 3px;
  word-break: break-word;
}

.live-action {
  margin-left: auto;
  color: var(--text);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  text-decoration: none;
  border-bottom: 1px solid var(--primary);
}

/* ── Loading ─────────────────────────────────────────────────────────────── */

/* Set by admin.js on the button that submitted a form, until the next page
   replaces this one. The label is hidden rather than swapped so the button
   keeps its width and the row does not jump. The spinner is square, like
   everything else here, and takes the colour the label would have had. */
button[data-loading], a.small-btn[data-loading] {
  --spin: var(--on-text);
  position: relative;
  color: transparent !important;
  cursor: progress;
  pointer-events: none;
  transform: translate(2px, 2px);
  box-shadow: 2px 2px 0 0 var(--primary);
}

button.destructive[data-loading] { --spin: #000; box-shadow: 2px 2px 0 0 var(--text); }
.signout button[data-loading] { --spin: var(--muted); transform: none; box-shadow: none; }

button[data-loading]::after, a.small-btn[data-loading]::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 12px;
  height: 12px;
  margin: auto;
  border: 2px solid var(--spin);
  border-right-color: transparent;
  animation: spin 0.7s linear infinite;
}

/* A disabled button would otherwise fade, which reads as "not allowed" rather
   than "working on it". */
button[data-loading]:disabled { opacity: 1; }

@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  button[data-loading]::after, a.small-btn[data-loading]::after { animation-duration: 2s; }
}

.live-held { background: var(--warn-bg); color: var(--warn-text); border-left-color: var(--warn-text); }
