/* RebelText operator console.
 *
 * One stylesheet, no build step, no framework. The look is the customer
 * portal's design language (DESIGN.md at the repository root): Geist for the
 * interface, Geist Mono for IDs, amounts and the small uppercase labels
 * (the portal sets those in JetBrains Mono; one mono face is a lighter load),
 * Barlow Condensed for titles and big figures, square
 * corners, and the brand red as the one accent — a fill on the primary action
 * only. The token values below are the portal's; change them together.
 *
 * Dark is the default. Light 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.
 *
 * The CSP is default-src 'self', so the fonts are served from /static, copied
 * from the portal's. */

@font-face { font-family: "Geist"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/static/font-geist.woff2") format("woff2"); }
@font-face { font-family: "Geist Mono"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/static/font-geist-mono.woff2") format("woff2"); }
@font-face { font-family: "Barlow Condensed"; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/font-barlow-condensed-600.woff2") format("woff2"); }
@font-face { font-family: "Barlow Condensed"; font-style: normal; font-weight: 700; font-display: swap; src: url("/static/font-barlow-condensed-700.woff2") format("woff2"); }

:root {
  color-scheme: dark;

  --bg: #0c0c0b;
  --surface: #131312;
  --surface-2: #181817;
  --hover: #1a1a18;
  --active: #1f1e1c;
  --input: #0c0c0b;
  --border: #252422;
  --border-strong: #33322e;
  --text: #eeeae0;
  --muted: #a09b90;
  --faint: #6c685f;

  --primary: #f04a2e;
  --primary-hover: #d83c1f;
  --rose: #ff6e52;
  --accent-bg: rgb(240 74 46 / 10%);

  --on-text: #0c0c0b;
  --topbar: rgb(12 12 11 / 92%);
  --topbar-control: 36px;
  --sidebar-w: 216px;
  --control-h: 40px;
  --control-h-sm: 32px;

  --logo-on-dark: block;
  --logo-on-light: none;

  --danger: #ff6e52;
  --danger-bg: rgb(240 74 46 / 7%);
  --danger-line: rgb(240 74 46 / 32%);
  --ok-bg: rgb(111 207 138 / 10%);
  --ok-text: #6fcf8a;
  --warn-bg: rgb(229 168 70 / 12%);
  --warn-text: #e5a846;
  --info-bg: #1a1a18;
  --info-text: #eeeae0;

  /* Flat, as the portal is: the only shadows are the primary button's hard red
     offset and the soft one under a dialog or menu. */
  --panel: linear-gradient(180deg, var(--surface-2), var(--surface));
  --press: 3px 3px 0 rgb(240 74 46 / 35%);
  --press-hover: 4px 4px 0 rgb(240 74 46 / 35%);
  --shadow: 0 16px 48px rgb(0 0 0 / 55%);
  --scrim: rgb(0 0 0 / 60%);
  --radius: 0;

  --font-body: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-heading: "Barlow Condensed", "Avenir Next Condensed", "Arial Narrow", sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --font-label: "Geist Mono", ui-monospace, "SF Mono", 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: #f3f1ea;
    --surface: #ffffff;
    --surface-2: #ffffff;
    --hover: #f6f3ec;
    --active: #eeeae0;
    --input: #ffffff;
    --border: #e1dccf;
    --border-strong: #cbc4b2;
    --text: #16140f;
    --muted: #57524a;
    --faint: #86806f;
    --primary: #e5401f;
    --primary-hover: #cc3517;
    --rose: #bf3418;
    --accent-bg: rgb(229 64 31 / 8%);
    --on-text: #ffffff;
    --topbar: rgb(243 241 234 / 92%);

    --danger: #bf3418;
    --danger-bg: #fff1ec;
    --danger-line: #f0b9a7;
    --ok-bg: rgb(28 117 54 / 9%);
    --ok-text: #1c7536;
    --warn-bg: rgb(196 128 10 / 12%);
    --warn-text: #9a5f00;
    --info-bg: #f6f3ec;
    --info-text: #16140f;

    --shadow: 0 16px 40px rgb(24 22 15 / 12%);
    --scrim: rgb(24 22 15 / 35%);
    --logo-on-dark: none;
    --logo-on-light: block;
  }
}

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

  --bg: #f3f1ea;
  --surface: #ffffff;
  --surface-2: #ffffff;
  --hover: #f6f3ec;
  --active: #eeeae0;
  --input: #ffffff;
  --border: #e1dccf;
  --border-strong: #cbc4b2;
  --text: #16140f;
  --muted: #57524a;
  --faint: #86806f;
  --primary: #e5401f;
  --primary-hover: #cc3517;
  --rose: #bf3418;
  --accent-bg: rgb(229 64 31 / 8%);
  --on-text: #ffffff;
  --topbar: rgb(243 241 234 / 92%);

  --danger: #bf3418;
  --danger-bg: #fff1ec;
  --danger-line: #f0b9a7;
  --ok-bg: rgb(28 117 54 / 9%);
  --ok-text: #1c7536;
  --warn-bg: rgb(196 128 10 / 12%);
  --warn-text: #9a5f00;
  --info-bg: #f6f3ec;
  --info-text: #16140f;

  --shadow: 0 16px 40px rgb(24 22 15 / 12%);
  --scrim: rgb(24 22 15 / 35%);
  --logo-on-dark: none;
  --logo-on-light: block;
}

* { box-sizing: border-box; }

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

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

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;
  height: 100vh;
  padding: 0;
  background: linear-gradient(180deg, var(--surface-2), var(--bg));
  border-right: 1px solid var(--border);
  font-family: var(--font-label);
  /* The fold button hangs over the right edge; the nav scrolls on its own. */
  overflow: visible;
}

/* 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 with the "admin" tag beside it, in a head as tall as the top
   bar: the customer console carries the same lockup without the tag, so the
   tag is what tells the two apart. */
.sidebar-head {
  display: flex;
  align-items: center;
  flex: none;
  height: 64px;
  padding: 0 18px;
  border-bottom: 1px solid var(--border);
}

/* 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: 116px; height: 26px; }
.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-label);
  text-transform: uppercase;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: #fff;
  background: var(--primary);
}

.sidebar nav {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px 10px;
}

/* A row: the icon in its own square, then the name. */
.sidebar nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 36px;
  padding: 0 6px;
  color: var(--muted);
  font-size: 13px;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.sidebar nav a:hover { color: var(--text); background: var(--hover); }

.nav-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 26px;
  height: 26px;
  color: var(--faint);
}

/* The page being shown: a red edge on the row and the row washed red — not
   the customer console's red icon square, one of the things that tells the
   two apart at a glance. */
.sidebar nav a.active { color: var(--text); background: var(--accent-bg); box-shadow: inset 3px 0 0 var(--primary); font-weight: 500; }
.sidebar nav a.active .nav-icon { color: var(--primary); }

/* Telling this console from the customer one at a glance: its sidebar stays
   ink black in both themes, where the customer console's follows the theme,
   and its lockup carries the ADMIN tag. */
.sidebar {
  --bg: #0c0c0b;
  --surface: #131312;
  --surface-2: #181817;
  --border: #252422;
  --border-strong: #33322e;
  --text: #eeeae0;
  --muted: #a09b90;
  --faint: #6c685f;
  --hover: #1a1a18;
  --active: #1f1e1c;
  --logo-on-dark: block;
  --logo-on-light: none;
  color-scheme: dark;
  color: var(--text);
}

/* A group's name: small, quiet, and not a link. */
.sidebar .nav-label {
  margin: 16px 8px 6px;
  color: var(--faint);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}
.sidebar .nav-label:first-child { margin-top: 0; }

/* The fold button, half over the sidebar's right edge. Desktop only. */
.nav-collapse {
  position: absolute;
  top: 21px;
  right: -11px;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  min-height: 0;
  padding: 0;
  border: 1px solid var(--border-strong);
  background: var(--bg);
  color: var(--faint);
  box-shadow: none;
}
.nav-collapse[hidden] { display: none; }
.nav-collapse:hover { color: var(--text); border-color: var(--primary); background: var(--bg); box-shadow: none; transform: none; }
.nav-collapsed .nav-collapse svg { transform: rotate(180deg); }
@media (min-width: 861px) {
  .nav-collapsed { --sidebar-w: 60px; }
  .nav-collapsed .sidebar-head { justify-content: center; padding: 0; }
  .nav-collapsed .brand { width: 27px; overflow: hidden; justify-content: flex-start; }
  .nav-collapsed .brand span { display: none; }
  .nav-collapsed .brand .logo { max-width: none; }
  .nav-collapsed .sidebar .nav-label { height: 1px; margin: 12px 8px; overflow: hidden; background: var(--border); color: transparent; }
  .nav-collapsed .sidebar .nav-label:first-child { display: none; }
  .nav-collapsed .sidebar nav a { justify-content: center; padding: 0; font-size: 0; }
}
@media (max-width: 860px) {
  .nav-collapse { display: none; }
}

/* ── 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;
  height: 100vh;
  padding: 0;
  background: linear-gradient(180deg, var(--surface-2), var(--bg));
  border-right: 1px solid var(--border);
  font-family: var(--font-label);
  /* The fold button hangs over the right edge; the nav scrolls on its own. */
  overflow: visible;
}

/* 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 with the "admin" tag beside it, in a head as tall as the top
   bar: the customer console carries the same lockup without the tag, so the
   tag is what tells the two apart. */
.sidebar-head {
  display: flex;
  align-items: center;
  flex: none;
  height: 64px;
  padding: 0 18px;
  border-bottom: 1px solid var(--border);
}

/* 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: 116px; height: 26px; }
.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-label);
  text-transform: uppercase;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: #fff;
  background: var(--primary);
}

/* 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. */

/* ── 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);
  padding: 0 9px 0 5px;
  border: 1px solid var(--border);
  color: var(--muted);
  cursor: pointer;
  list-style: none;
}
.account-menu summary::-webkit-details-marker { display: none; }
.account-menu summary:hover,
.account-menu[open] summary { color: var(--text); border-color: var(--border-strong); }
.account-menu summary.active { border-bottom-color: var(--primary); }
.account-menu summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* The operator's initials: small and round in the bar, large and square at
   the head of its menu. */
.avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--active);
  color: var(--text);
  font-family: var(--font-label);
  font-size: 10px;
  font-weight: 600;
}
.avatar.large {
  width: 36px;
  height: 36px;
  border-radius: 0;
  background: var(--primary);
  color: #fff;
  font-family: var(--font-heading);
  font-size: 18px;
  font-weight: 700;
}
.caret {
  width: 5px;
  height: 5px;
  margin: -3px 2px 0;
  border-right: 1.5px solid var(--faint);
  border-bottom: 1.5px solid var(--faint);
  transform: rotate(45deg);
  transition: transform 0.15s;
}
.account-menu[open] .caret { transform: rotate(-135deg); margin-top: 3px; }

.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;
  align-items: center;
  gap: 12px;
  padding: 14px;
  border-bottom: 1px solid var(--border);
}
.menu-id { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.account-menu .menu > a {
  padding: 10px 14px;
  color: var(--text);
  text-decoration: none;
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: 0.08em;
  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-label);
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: 0.08em;
  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 menu button, the brand
   and the theme, and the sidebar as a drawer from the left 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 auto 0 0;
    width: min(280px, 86vw);
    height: 100dvh;
    background: var(--surface);
    border-right: 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;
    flex: none;
    border-top: 1px solid var(--border);
  }
  .drawer-account .menu-who { padding: 14px 16px 10px; border-bottom: 0; flex-direction: column; align-items: flex-start; gap: 2px; }
  .drawer-account > a {
    padding: 10px 16px;
    color: var(--text);
    text-decoration: none;
    font-family: var(--font-label);
    font-weight: 500;
    font-size: 11.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }
  .drawer-account > a:hover { background: var(--hover); }
  /* The theme moves into the drawer, as a row beside Profile settings: the
     top bar has no room for it on a phone. */
  .shell .topbar .theme-toggle { display: none; }
  .drawer-theme {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    width: 100%;
    height: auto;
    min-height: 0;
    padding: 10px 16px;
    border: 0;
    background: transparent;
    color: var(--text);
    text-align: left;
    box-shadow: none;
    transform: none;
    cursor: pointer;
    font-family: var(--font-label);
    font-weight: 500;
    font-size: 11.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }
  .drawer-theme svg { flex: none; color: var(--muted); }
  .drawer-theme:hover { background: var(--hover); box-shadow: none; transform: none; }
  .drawer-theme[hidden] { display: none; }

  .drawer-account > a.active { background: var(--accent-bg); 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; }
  /* The menu button leads the bar, as in the portal: the drawer opens from
     the side it sits on. */
  .menu-toggle { margin-left: -4px; border: 0; }
}

@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;
  color: var(--text);
}

h1 { font-size: 36px; font-weight: 700; letter-spacing: 0.02em; line-height: 0.95; margin: 0 0 24px; }
h2 { font-size: 22px; 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-label);
  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 {
  font-family: var(--font-label);
  color: var(--rose);
  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(--panel);
  border: 1px solid var(--border);
}

/* 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-value {
  font-family: var(--font-heading);
  font-size: 36px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.stat-label {
  order: -1;
  font-family: var(--font-label);
  color: var(--faint);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

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

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

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

th {
  font-family: var(--font-label);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  background: var(--surface-2);
  border-bottom-color: var(--border-strong);
}

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

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-family: var(--font-label);
  font-size: 10.5px;
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: 0.06em;
  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;
}

/* The same sizes as the customer console's: inputs a half-step up from the
   body text. */
input, select { font-size: 13.5px; }

input, select, button {
  font: inherit;
  min-height: var(--control-h);
  padding: 0 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: var(--faint); }

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; }
/* The control height is for boxes you type in; a tick box keeps its own size,
   including inside a .form-grid, which stretches inputs to the column. */
input[type="checkbox"], input[type="radio"] { min-height: 0; accent-color: var(--primary); }
.form-grid input[type="checkbox"] { width: auto; }
.form-grid .check { justify-self: start; }

/* 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(--bg);
  color: var(--text);
  border-color: var(--border-strong);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease, border-color 0.12s ease;
}

button:hover { background: var(--hover); border-color: var(--faint); }

/* The one red action in a view, with the portal's hard offset that lifts on
   hover and presses in on click. */
button.primary { background: var(--primary); border-color: var(--primary); color: #fff; box-shadow: var(--press); }
button.primary:hover { background: var(--primary); border-color: var(--primary); transform: translate(-1px, -1px); box-shadow: var(--press-hover); }
button.primary:active { transform: translate(2px, 2px); 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: 12px;
  min-height: 0;
  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: 22px;
  background: var(--panel);
  border: 1px solid var(--border);
}

/* 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-family: var(--font-label);
  font-size: 11px;
  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);
  border-color: var(--primary);
  color: #fff;
  box-shadow: var(--press);
}
.login button:hover { background: var(--primary); transform: translate(-1px, -1px); box-shadow: var(--press-hover); }

/* 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 {
  font-family: var(--font-label);
  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 {
  font-family: var(--font-label);
  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(--rose); 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: 12.5px; }
.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(--rose); }

p a, .notfound a { color: var(--rose); 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: 700;
  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: var(--scrim); }
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(--panel);
  border: 1px solid var(--border);
}

.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-family: var(--font-label);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.12em;
  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;
  min-height: var(--control-h-sm);
  padding: 0 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: var(--scrim); }
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: 22px; 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: 13px;
  min-height: 36px;
  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 {
  font-family: var(--font-label);
  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(--danger);
}

details.danger[open] summary { margin-bottom: 10px; }
details.danger {
  border-color: var(--danger-line);
  background: var(--danger-bg);
}

button.destructive {
  background: var(--bg);
  color: var(--danger);
  border-color: var(--danger-line);
}

button.destructive:hover { background: var(--danger-bg); border-color: var(--danger); }

button.small-btn, a.small-btn {
  min-height: var(--control-h-sm);
  padding: 0 10px;
  font-size: 12.5px;
}

/* A link dressed as a button, for actions that navigate rather than post. */
a.small-btn {
  display: inline-flex;
  align-items: center;
  font-weight: 500;
  white-space: nowrap;
  text-decoration: none;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border-strong);
}

a.small-btn:hover { color: var(--text); background: var(--hover); border-color: var(--faint); }

/* ── 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. */
/* One line that scrolls sideways when it must: a strip that wraps puts the
   active tab's underline in the middle of the block. */
.tabs {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: thin;
  gap: 0;
  margin: 0 0 32px;
  border-bottom: 1px solid var(--border-strong);
}

.tab {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  color: var(--muted);
  text-decoration: none;
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-bottom: 2px 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-label);
  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: #fff;
}

@media (max-width: 700px) {
  main { padding: 28px 16px 64px; }
  h1 { font-size: 32px; }
}

/* 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; min-height: var(--control-h-sm); padding: 0 8px; font-size: 12px; }

/* ── 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-family: var(--font-label);
  font-size: 11px;
  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);
}

/* A portal login address another account holds, with the offer to move it.
   Shaped like the secret banner, in the warning colour: it is a decision, not a
   result. */
.taken-login {
  padding: 12px 16px;
  margin: 0 0 20px;
  background: var(--warn-bg);
  border: 1px solid var(--warn-text);
  border-left-width: 4px;
}
.taken-login h3 { margin: 0 0 4px; font-size: 15px; color: var(--warn-text); }
.taken-login p { margin: 0 0 10px; }

/* 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(--text);
  position: relative;
  color: transparent !important;
  cursor: progress;
  pointer-events: none;
}

button.primary[data-loading], .login button[data-loading] { --spin: #fff; }
button.destructive[data-loading] { --spin: var(--danger); }
.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); }

/* ── Settings rows ───────────────────────────────────────────────────────── */

/* The account's Billing & pricing tab: each setting one row of label, current
   value and the button that opens its dialog, grouped in panels. */
.settings { display: flex; flex-direction: column; gap: 20px; }
.settings > section { margin: 0; }

.setting-group { background: var(--panel); border: 1px solid var(--border); }
.setting-group-danger { border-color: var(--danger-line); }

.setting-group-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 22px 14px;
}
.setting-group-head h2 { margin: 0; }
.setting-group-head p { margin: 6px 0 0; }

.setting {
  display: grid;
  grid-template-columns: minmax(150px, 220px) minmax(0, 1fr) auto;
  gap: 16px;
  align-items: center;
  padding: 14px 22px;
  border-top: 1px solid var(--border);
}
.setting-group-danger .setting { border-top: 0; }

.setting-label {
  font-family: var(--font-label);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.setting-value { min-width: 0; overflow-wrap: anywhere; }
.setting-note { display: block; margin-top: 2px; color: var(--muted); font-size: 12.5px; }
.setting-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; align-items: center; }

@media (max-width: 700px) {
  .setting { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .setting-actions { justify-content: flex-start; }
}

/* A quiet outlined label beside a value: where it comes from, not a status. */
.tag {
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid var(--border-strong);
  color: var(--muted);
  font-family: var(--font-label);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* A choice among a few, each a box that says what it means, the radio inside. */
.choices { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 0; border: 0; }
.choices legend {
  margin-bottom: 8px;
  padding: 0;
  font-family: var(--font-label);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.choice {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--border-strong);
  cursor: pointer;
}
.choice:has(input[type="radio"]:checked) { border-color: var(--primary); background: var(--accent-bg); }
/* .form-grid stretches every input to the column; a radio and a date in a
   choice keep their own size. */
.form-grid .choice input[type="radio"], .choice input[type="radio"] { flex: none; width: auto; min-height: 0; margin: 0; accent-color: var(--primary); }
.form-grid .choice input[type="date"] { flex: none; width: auto; }
.choice > span { flex: 1; }
.choice .field-hint { margin-top: 2px; }
.choice input[type="date"] { min-height: var(--control-h-sm); }

/* A secondary way in, folded away until it is wanted. */
.disclose > summary { cursor: pointer; color: var(--muted); font-size: 12.5px; }
.disclose > summary:hover { color: var(--text); }
.disclose[open] > summary { margin-bottom: 10px; }

/* ── Pricing: ladders ────────────────────────────────────────────────────── */

/* The ladder list beside the one ladder open. The list scrolls on its own, so
   fifty ladders do not push the open one off the screen. */
.ladders {
  display: grid;
  grid-template-columns: minmax(200px, 260px) minmax(0, 1fr);
  border: 1px solid var(--border);
  background: var(--panel);
}
.ladder-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border-right: 1px solid var(--border);
}
.ladder-list input[type="search"] { width: 100%; }
.ladder-list ul { margin: 0; padding: 0; list-style: none; max-height: 420px; overflow-y: auto; }
.ladder-list li a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 36px;
  padding: 0 10px;
  color: var(--text);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.ladder-list li a:hover { background: var(--hover); }
.ladder-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ladder-list li a.active { background: var(--accent-bg); box-shadow: inset 3px 0 0 var(--primary); }
.ladder-list p { margin: 0; }

.ladder-detail { min-width: 0; }
.ladder-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 22px;
}
.ladder-head h3 { margin: 0 0 4px; font-size: 22px; overflow-wrap: anywhere; }
.ladder-detail table { border-width: 1px 0; }
.ladder-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 22px;
}
.ladder-foot input[type="text"] { flex: 1; min-width: 220px; }
.ladder-foot .muted { flex: 1; }

@media (max-width: 760px) {
  .ladders { grid-template-columns: minmax(0, 1fr); }
  .ladder-list { border-right: 0; border-bottom: 1px solid var(--border); }
  .ladder-list ul { max-height: 220px; }
}

.stat .muted.small { font-size: 12px; }

/* A few short rules under a form, one line each, instead of a paragraph. */
.hints { margin: 12px 0 0; padding-left: 18px; color: var(--muted); font-size: 12.5px; }
.hints li + li { margin-top: 4px; }
.hints strong { color: var(--text); font-weight: 500; }

.form-modal .field .field-hint, .form-grid .field .field-hint { max-width: none; }

.ladder-meta { display: flex; align-items: center; gap: 8px; flex: none; }
.ladder-accounts { padding: 18px 22px 22px; border-top: 1px solid var(--border); }
.ladder-accounts h4 {
  margin: 0 0 10px;
  font-family: var(--font-label);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}
.ladder-accounts table { border-width: 1px; }
.ladder-accounts td code { display: block; }
.ladder-accounts .table-scroll { max-height: 420px; overflow-y: auto; }

/* A threshold that cannot change, shown as text where the others are boxes. */
.fixed-value { display: inline-flex; align-items: center; min-height: var(--control-h-sm); gap: 4px; }

/* ── Lists ───────────────────────────────────────────────────────────────── */

/* Every list page: search, a status picked with one click, and the count. */
.list-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 0 0 16px;
}
.list-search { display: flex; gap: 8px; flex: 1 1 280px; max-width: 460px; }
.list-search input[type="search"] { flex: 1; min-width: 0; }
.list-count { margin-left: auto; }
.list-count a { color: var(--rose); text-decoration: none; }

/* A segmented control made of links: one is current. */
.seg { display: inline-flex; border: 1px solid var(--border-strong); }
.seg a {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 0 12px;
  color: var(--muted);
  text-decoration: none;
  font-family: var(--font-label);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.seg a + a { border-left: 1px solid var(--border-strong); }
.seg a:hover { color: var(--text); background: var(--hover); }
.seg a[aria-current] { color: var(--text); background: var(--accent-bg); box-shadow: inset 0 -2px 0 var(--primary); }

/* A list's first column: the name to click, its ID and anything else under. */
table.list .cell-main > * { display: block; }
table.list .cell-main code { margin-top: 2px; }
.trunc { display: block; max-width: 34ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
td.nowrap, th.nowrap { white-space: nowrap; }

.list-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}
.list-pager > .muted { margin-right: 8px; font-size: 12.5px; }
.small-btn.disabled { display: inline-flex; align-items: center; min-height: var(--control-h-sm); padding: 0 10px; font-size: 12.5px; color: var(--faint); border: 1px solid var(--border); }

/* The one red action of a page, as a link. */
a.small-btn.primary { background: var(--primary); border-color: var(--primary); color: #fff; box-shadow: var(--press); }
a.small-btn.primary:hover { background: var(--primary); color: #fff; transform: translate(-1px, -1px); box-shadow: var(--press-hover); }
.small.block, .block { display: block; }
table.list .row-buttons { flex-wrap: nowrap; }

/* ── Overview ────────────────────────────────────────────────────────────── */

.overview-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 28px;
}
.overview-head h1 { margin: 0 0 8px; }
.overview-head p { margin: 0; }
.overview-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }

/* A panel with a titled head; its content runs to its edges. */
.panel-block { background: var(--panel); border: 1px solid var(--border); margin-bottom: 28px; }
.block-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 22px 12px;
}
.block-head h2 { margin: 0; }
.block-head p { margin: 6px 0 0; }
.block-empty { margin: 0; padding: 4px 22px 20px; }

/* A small uppercase heading over tiles, lighter than a panel's title. */
.lab-head {
  display: block;
  margin: 0 0 12px;
  font-family: var(--font-label);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}

/* Tiles: hairlines between them are the gaps, so they stay even however the
   row wraps. Six go one row, then 3 × 2, then 2 × 3; three go one row, then
   one each. */
.tiles {
  display: grid;
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  margin-bottom: 10px;
}
.tiles-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.tiles-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1320px) { .tiles-6 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 640px) {
  .tiles-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tiles-3 { grid-template-columns: minmax(0, 1fr); }
}
.tile { display: flex; flex-direction: column; gap: 6px; padding: 18px 22px; background: var(--surface); min-width: 0; }
.tile .stat-value { font-size: 40px; }
.tile-delta { font-family: var(--font-mono); font-size: 12px; }
.tile-delta .muted { font-family: var(--font-body); }
.tile-delta.good { color: var(--ok-text); }
.tile-delta.bad { color: var(--danger); }
section + section, .chart-block { margin-top: 0; }
.tiles + .muted { margin: 0 0 20px; }

/* The traffic chart: an SVG of rects whose geometry is set in attributes. */
.chart-block { padding: 18px 22px; }
.chart-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.chart-head .lab-head { margin: 0; }
.chart-legend { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; }
.chart-legend svg { vertical-align: -1px; margin-right: 4px; }
.chart { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 8px; }
.chart-axis {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 200px;
  text-align: right;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted);
}
.chart-plot { display: block; width: 100%; height: 200px; border-bottom: 1px solid var(--border-strong); }
.chart-plot .grid { stroke: var(--border); }
.chart-x { display: flex; justify-content: space-between; padding-left: 64px; margin-top: 6px; font-family: var(--font-mono); font-size: 12px; }
svg .bar-del { fill: var(--text); fill-opacity: 0.8; }
svg .bar-flight { fill: var(--faint); }
svg .bar-fail { fill: var(--primary); }

/* A thin bar beside a figure: a rate, or a share of the largest. */
.meter { display: block; width: 100%; height: 4px; margin-top: 6px; }
.meter .meter-track { fill: var(--border); }
.meter .meter-fill { fill: var(--text); fill-opacity: 0.8; }
.meter .meter-fill.bad { fill: var(--primary); fill-opacity: 1; }

/* Needs attention: a count, what it is and why it matters; open for the list. */
.attention { border-top: 1px solid var(--border); }
.attention > summary {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
  padding: 12px 22px;
  cursor: pointer;
  list-style: none;
}
.attention > summary::-webkit-details-marker { display: none; }
.attention > summary:hover { background: var(--hover); }
.attention-count { font-family: var(--font-heading); font-weight: 700; font-size: 28px; line-height: 1; text-align: right; }
.attention-text { min-width: 0; }
.attention-title { display: block; font-weight: 500; }
.attention-bad { color: var(--danger); background: var(--danger-bg); }
.attention-warn { color: var(--warn-text); background: var(--warn-bg); }
.attention-plain { color: var(--muted); }
.attention[open] .pill { color: var(--text); }
.attention-rows { margin: 0; padding: 0 22px 12px 110px; list-style: none; }
.attention-rows a {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) auto;
  gap: 16px;
  align-items: baseline;
  padding: 8px 0;
  border-top: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
}
.attention-rows a:hover { color: var(--rose); }
.attention-figure { font-family: var(--font-mono); font-size: 12.5px; text-align: right; white-space: nowrap; }
.attention-more { margin: 0; padding: 0 22px 14px 110px; }
.attention-more a { color: var(--rose); text-decoration: none; }
.attention-clear { margin: 0; padding: 14px 22px 18px; border-top: 1px solid var(--border); }
@media (max-width: 700px) {
  .attention > summary { grid-template-columns: 48px minmax(0, 1fr); }
  .attention > summary .pill { display: none; }
  .attention-rows, .attention-more { padding-left: 22px; }
  .attention-rows a { grid-template-columns: minmax(0, 1fr) auto; }
  .attention-rows a > .muted { display: none; }
}

/* Two or three panels side by side, stacking on a narrow screen. */
.overview-row { display: flex; flex-wrap: wrap; gap: 20px; align-items: stretch; margin-bottom: 28px; }
.overview-row > .panel-block { margin-bottom: 0; min-width: 0; }
.grow-3 { flex: 3 1 520px; }
.grow-2 { flex: 2 1 360px; }
.grow-1 { flex: 1 1 300px; }
table.flush { border-width: 1px 0 0; }
.mono-num { font-family: var(--font-mono); font-size: 12.5px; white-space: nowrap; }

.failures { margin: 0; padding: 0 22px 18px; list-style: none; display: flex; flex-direction: column; gap: 14px; }
.failure-line { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }

.figures { margin: 0; padding: 0 22px 18px; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px 16px; align-items: baseline; }
.figures dt { color: var(--muted); }
.figures dd { margin: 0; text-align: right; font-family: var(--font-mono); font-size: 12.5px; }

.activity { margin: 0; padding: 0 22px 14px; list-style: none; }
.activity li { display: flex; flex-direction: column; gap: 2px; padding: 9px 0; border-top: 1px solid var(--border); }
.activity code { color: var(--text); }
.activity .trunc { display: inline-block; max-width: 22ch; vertical-align: bottom; }

/* The account page header: name and ID on the left, what is done to an
   account most on the right, on every tab. */
.account-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
.account-name { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.account-name h1 { margin: 0; }
.account-id { display: flex; align-items: center; gap: 8px; margin-top: 8px; color: var(--muted); }
.account-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.secret-head { display: flex; align-items: baseline; gap: 10px; }
.secret-head .copy-btn { margin-left: auto; }
.list-note { margin: 0 0 16px; }

.tiles-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1100px) { .tiles-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .tiles-4 { grid-template-columns: minmax(0, 1fr); } }
.tiles-4 .tile .stat-value { font-size: 30px; overflow-wrap: anywhere; }
.tiles-4 + .panel-block { margin-top: 18px; }

/* Ready to send?: one line per requirement, its mark coloured by state. */
.ready { display: grid; grid-template-columns: 24px minmax(120px, 220px) minmax(0, 1fr) auto; gap: 12px; align-items: baseline; padding: 10px 22px; border-top: 1px solid var(--border); }
.ready-mark { font-family: var(--font-mono); font-weight: 600; text-align: center; }
.ready-ok .ready-mark { color: var(--ok-text); }
.ready-warn .ready-mark { color: var(--warn-text); }
.ready-bad .ready-mark { color: var(--danger); }
.ready-none .ready-mark { color: var(--faint); }
.ready-label { font-weight: 500; }
@media (max-width: 700px) {
  .ready { grid-template-columns: 24px minmax(0, 1fr) auto; }
  .ready > .muted { grid-column: 2 / -1; }
}

.mix { padding: 4px 22px 20px; }
.mix-bar { display: block; width: 100%; height: 24px; background: var(--input); }
.mix-figures { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin: 14px 0 0; }
.mix-figures dd { margin: 4px 0 0; font-family: var(--font-mono); font-size: 18px; }
@media (max-width: 520px) { .mix-figures { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.activity { margin: 0; padding: 0 22px 18px; list-style: none; display: flex; flex-direction: column; gap: 12px; }
.activity li > * { display: block; }

/* Numbers: how many it may hold, beside the two ways to change that. */
.capacity { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 16px; padding: 18px 22px; }
.capacity .lab-head { margin-bottom: 6px; }
.capacity-figure { margin: 0 0 4px; }
.capacity-figure .stat-value { font-size: 30px; }
.capacity p:last-child { margin: 0; }

.choices-3 { flex-direction: row; flex-wrap: wrap; }
.choices-3 .choice { flex: 1 1 140px; }

/* Sign in as customer: one row per active login. */
.signin-row { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-top: 1px solid var(--border); }
.signin-row .cell-main { flex: 1; min-width: 0; }
.signin-row .cell-main > * { display: block; }
.form-grid .signin-row select { width: auto; }
.account-id .copy-btn { min-height: 24px; padding: 0 8px; font-size: 11.5px; }

/* Labelled facts under a setting, one per line, so a long reason or a date
   never runs into the sentence around it. */
.facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px 16px; margin: 10px 0 0; font-size: 13px; }
.facts dt { font-family: var(--font-label); font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); padding-top: 2px; }
.facts dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }

/* A button that can't be used now looks it: faint, flat, and the cursor says
   no. A submitting button (data-loading) keeps its look; it is busy, not barred. */
button:disabled:not([data-loading]),
button:disabled:not([data-loading]):hover {
  color: var(--faint);
  background: transparent;
  border-color: var(--border);
  box-shadow: none;
  cursor: not-allowed;
}

/* An add-on change's quote, in a dialog over its tab. */
.confirm-lede { margin: 0; padding: 14px 20px; }
.confirm-figures { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; margin: 0; background: var(--border); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.confirm-figures > div { padding: 16px 20px; background: var(--panel); }
.confirm-figures dd { margin: 6px 0 0; }
.confirm-figures .stat-value { font-size: 30px; }
.confirm-notes { padding: 14px 20px 0; }
.confirm-notes p { margin: 0 0 8px; }
@media (max-width: 520px) { .confirm-figures { grid-template-columns: minmax(0, 1fr); } }
/* The next bill in an add-on quote: Stripe's lines, then the total. */
.confirm-renewal { padding: 14px 20px 0; border-bottom: 1px solid var(--border); }
.confirm-renewal .lab-head { margin-bottom: 8px; }
.bill-lines { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 16px; margin: 0 0 10px; font-size: 13px; }
.bill-lines dt { color: var(--muted); }
.bill-lines dd { margin: 0; text-align: right; font-family: var(--font-mono); font-size: 12.5px; }
.bill-lines .bill-total { padding-top: 6px; border-top: 1px solid var(--border); color: var(--text); font-weight: 600; }

/* A message's failure, said once: what, where and when, and the carrier's
   own words only when they add to the reason. */
.failure { margin: 0 0 20px; padding: 14px 18px; background: var(--danger-bg); border: 1px solid var(--danger-line); border-left: 4px solid var(--danger); }
.failure p { margin: 0 0 4px; }
.failure-what { font-size: 15px; color: var(--danger); }
.failure-what strong { font-family: var(--font-mono); font-weight: 600; }
.failure pre.body { margin: 10px 0 4px; }
.tiles-4 + .panel-block, .failure + .tiles { margin-top: 0; }
.tiles + .panel-block { margin-top: 20px; }

/* Content inside a panel that has no table to give it edges. */
.block-pad { padding: 0 22px 18px; }
.block-pad > :last-child { margin-bottom: 0; }

/* Facts in a panel: label column, value column, values in the text colour. */
.facts-panel { margin: 0; padding: 0 22px 18px; gap: 8px 18px; }
.facts-panel dd { color: var(--text); }
.facts-panel dd .muted { color: var(--muted); }
.panel-block .timeline { border: 0; background: transparent; padding: 4px 22px 8px; }
.form-row-end { display: flex; justify-content: flex-end; }
.receipt-raw { padding-top: 14px; border-top: 1px solid var(--border); }

/* Search, in the top bar on every page. The sidebar's Search entry is for a
   phone, where the top bar has no room for the box. */
.topbar-search { margin-right: auto; flex: 0 1 420px; }
.topbar-search input { width: 100%; height: var(--topbar-control); }
.nav-search { display: none !important; }
@media (max-width: 860px) {
  .topbar-search { display: none; }
  .nav-search { display: flex !important; }
}
.send-test { max-width: 640px; }
/* Two-factor enrolment: the code beside the ways to finish it. */
.enrol { max-width: 760px; margin-top: 20px; }
.enrol-body { display: flex; flex-wrap: wrap; gap: 24px; align-items: flex-start; }
.enrol-body .form-grid { flex: 1 1 320px; min-width: 0; }
.enrol .totp-secret { display: block; overflow-wrap: anywhere; padding: 8px 10px; background: var(--input); border: 1px solid var(--border-strong); }

/* Dialogs fade and rise in, and fade back out, backdrop with them. CSS only:
   @starting-style gives the opening frame, and overlay/display with
   allow-discrete keep a closing dialog in the top layer until it has faded. */
.form-modal, .log-modal {
  opacity: 0;
  transform: translateY(12px) scale(0.98);
  transition: opacity 0.18s ease, transform 0.18s ease,
              overlay 0.18s allow-discrete, display 0.18s allow-discrete;
}
.form-modal[open], .log-modal[open] { opacity: 1; transform: none; }
@starting-style {
  .form-modal[open], .log-modal[open] { opacity: 0; transform: translateY(12px) scale(0.98); }
}
.form-modal::backdrop, .log-modal::backdrop {
  opacity: 0;
  transition: opacity 0.18s ease, overlay 0.18s allow-discrete, display 0.18s allow-discrete;
}
.form-modal[open]::backdrop, .log-modal[open]::backdrop { opacity: 1; }
@starting-style {
  .form-modal[open]::backdrop, .log-modal[open]::backdrop { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .form-modal, .log-modal, .form-modal::backdrop, .log-modal::backdrop { transition: none; }
}

/* A hover hint, drawn from data-tip: the theme switch says what it is set to
   and what a click does. Shown on keyboard focus too, below its button and
   aligned to its right edge so it never runs off the screen. */
[data-tip] { position: relative; }
[data-tip]:hover::after, [data-tip]:focus-visible::after {
  content: attr(data-tip);
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 40;
  padding: 6px 9px;
  white-space: nowrap;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.4;
  text-transform: none;
  box-shadow: var(--shadow);
  pointer-events: none;
}

/* Whether a send reaches a handset, drawn as the customer console's sending
   indicator is: a chat icon, a pulsing dot and the state, green for Live and
   amber for Fake (VOLT_FAKE). */
.sending {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 28px;
  padding: 0 10px;
  color: var(--text);
  font-family: var(--font-label);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}
.sending svg { flex: none; color: var(--faint); }
.sending-label { color: var(--faint); }
.sending .dot {
  position: relative;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ok-text);
  box-shadow: 0 0 8px var(--ok-text);
}
.sending.fake .dot { background: var(--warn-text); box-shadow: 0 0 8px var(--warn-text); }
.sending.fake .sending-state { color: var(--warn-text); }
.sending .dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: inherit;
  animation: send-pulse 2.4s ease-out infinite;
}
@keyframes send-pulse {
  0% { transform: scale(1); opacity: 0.9; }
  70%, 100% { transform: scale(2.4); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .sending .dot::after { animation: none; } }
@media (max-width: 860px) {
  .sending-label { display: none; }
  .sending { padding: 0 4px; }
}

/* A narrow phone: the bar is packed from the left so the menu button can never
   be pushed off it, the send badge is its dot (its word kept for a screen
   reader, and its hint still says what it means), and the lockup a size down. */
@media (max-width: 860px) {
  .topbar { justify-content: flex-start; }
}
@media (max-width: 420px) {
  .sending svg { display: none; }
  .sending-state {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .account-menu .caret { display: none; }
  .account-menu summary { padding: 0 5px; }
  .topbar-brand .logo { width: 100px; height: 22px; }
}

/* The search page's own box, for a phone only: wider, the top bar has one. */
.page-search { display: none; }
@media (max-width: 860px) {
  .page-search { display: flex; gap: 8px; margin: 0 0 20px; }
  .page-search input { flex: 1; min-width: 0; }
}
.stat .stat-action { align-self: flex-start; margin-top: 6px; }
