/* Orbit: the shared design system (docs/revamp-brief.md, docs/ui-notes-v2.md).

   A control room for telephone traffic. Light by default: Mist background, white Paper panels
   with 1px Rule lines and no shadows, Ink text, one accent (Orbit's brand blue, css/contrast.css)
   for actions, selected navigation, links and focus. Status colours only ever mean status, and test mode has its own
   violet. Atkinson Hyperlegible Next for reading, Barlow Semi Condensed for titles, the Overview
   sentence, big figures and the caller IDs in the number strip. Radius follows hierarchy: 12px
   panels, 8px controls and tiles, 999px only on the count badge. Spacing is on a 4px grid.

   The CSP forbids inline styles: everything lives here and in the per-screen files under /css/.
   Sections: fonts, tokens, base, focus, shell (sidebar, top bar, bars, tabs, More), page head and
   sentence, type helpers, buttons, badges and lamps, panels, forms, tables, lists, number strip,
   charts, Overview, toasts and dialogs, popover, tour, motion. */

/* --------------------------------------------------------------------- fonts */
/* Self-hosted (SIL Open Font License, see /fonts/OFL-*.txt). Atkinson Hyperlegible Next is one
   variable file that covers 400 and 700. */
@font-face {
  font-family: "Atkinson Hyperlegible Next";
  src: url("/fonts/atkinson-hyperlegible-next.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Barlow Semi Condensed";
  src: url("/fonts/barlow-semi-condensed-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Barlow Semi Condensed";
  src: url("/fonts/barlow-semi-condensed-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* -------------------------------------------------------------------- tokens */
:root {
  color-scheme: light;
  --ink: #18212F;
  --slate: #5A6577;
  --mist: #F2F4F7;
  --paper: #FFFFFF;
  --rule: #DCE1E8;
  --rule-btn: #C9D0DA;
  --rule-strong: #8792A2;
  --hover: #F2F4F7;

  --accent: #0C5C97;
  --accent-hover: #0A4B7B;
  --accent-fill: #7DC8FF;
  --accent-fill-hover: #5AB4F6;
  --accent-tint: #E7F4FE;
  --on-accent: #10171F;

  --ok: #11795A;
  --ok-pale: #E3F4EC;
  --warn: #B4580B;
  --warn-text: #A14F0A;
  --warn-pale: #FDF0E3;
  --bad: #C42B3C;
  --bad-fill: #C42B3C;
  --bad-fill-hover: #A52232;
  --bad-pale: #FBE7EA;
  --test: #6B3FC4;
  --test-pale: #EFE9FB;
  --off: #5A6577;
  --off-pale: #EEF1F5;

  --series-1: #1F4FD6;
  --series-2: #0E8C9C;
  --series-3: #8A5CF6;
  --series-4: #C27C0E;
  --series-5: #5E6B7E;
  --series-6: #D14D8B;

  --scrim: rgba(24, 33, 47, 0.45);
  --tour-dim: rgba(24, 33, 47, 0.42);
  --float-shadow: 0 10px 28px rgba(24, 33, 47, 0.16);
  --toast-bg: #18212F;
  --toast-text: #FFFFFF;

  --font-body: "Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-display: "Barlow Semi Condensed", "Arial Narrow", "Atkinson Hyperlegible Next", system-ui, sans-serif;

  /* The status icons (.lamp and the toasts), as masks: the shape here, the colour from the state. */
  --lamp-ok: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8.6l3.1 3.1L13 4.8' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --lamp-warn: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M6.7 2.2a1.5 1.5 0 0 1 2.6 0l5.9 10.3A1.5 1.5 0 0 1 13.9 15H2.1a1.5 1.5 0 0 1-1.3-2.5zM7.1 5.6h1.8l-.25 4.4H7.35zM8 11.1a1 1 0 1 1 0 2 1 1 0 0 1 0-2z'/%3E%3C/svg%3E");
  --lamp-bad: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M5.2 1h5.6L15 5.2v5.6L10.8 15H5.2L1 10.8V5.2zM7.1 3.9h1.8l-.25 5.3H7.35zM8 10.4a1.05 1.05 0 1 1 0 2.1 1.05 1.05 0 0 1 0-2.1z'/%3E%3C/svg%3E");
  --lamp-test: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M5.6 1.2h4.8v1.6H9.7v3.4l3.9 6.5A1.5 1.5 0 0 1 12.3 15H3.7a1.5 1.5 0 0 1-1.3-2.3l3.9-6.5V2.8h-.7z'/%3E%3C/svg%3E");
  --lamp-off: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2.5' y='6.7' width='11' height='2.6' rx='1.3'/%3E%3C/svg%3E");
  --lamp-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8 1a7 7 0 1 1 0 14A7 7 0 0 1 8 1zM7.1 6.9h1.8v5.2H7.1zM8 3.6a1.05 1.05 0 1 1 0 2.1 1.05 1.05 0 0 1 0-2.1z'/%3E%3C/svg%3E");

  --tap: 44px;
  --gutter: 16px;
  --side-w: 232px;
  --tab-h: 62px;
  --page-max: 1200px;

}

/* Dark is chosen in Settings, Appearance (app.js sets data-theme). Deep slate panels, not black,
   with the same hues lightened for contrast. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --ink: #E6EAF0;
  --slate: #9AA6B7;
  --mist: #0E141D;
  --paper: #141B26;
  --rule: #253042;
  --rule-btn: #34405A;
  --rule-strong: #5F6C80;
  --hover: #1B2433;

  --accent: #7DC8FF;
  --accent-hover: #9ED3FA;
  --accent-fill: #7DC8FF;
  --accent-fill-hover: #9ED3FA;
  --accent-tint: #12283A;
  --on-accent: #0B0E12;

  --ok: #43C08F;
  --ok-pale: #11291F;
  --warn: #E99A4A;
  --warn-text: #EFA458;
  --warn-pale: #2D1F10;
  --bad: #F27382;
  --bad-fill: #D23A4C;
  --bad-fill-hover: #E04A5C;
  --bad-pale: #33161C;
  --test: #AE92F5;
  --test-pale: #231B3B;
  --off: #9AA6B7;
  --off-pale: #1C2433;

  --series-1: #7D9DFF;
  --series-2: #38C2D1;
  --series-3: #A98BFA;
  --series-4: #E2A640;
  --series-5: #9AA6B8;
  --series-6: #EE7FB3;

  --scrim: rgba(0, 0, 0, 0.6);
  --tour-dim: rgba(0, 0, 0, 0.55);
  --float-shadow: 0 10px 28px rgba(0, 0, 0, 0.5);
  --toast-bg: #E6EAF0;
  --toast-text: #18212F;
}

/* ---------------------------------------------------------------------- base */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; background: var(--mist); }
body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--mist);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  overflow-wrap: break-word;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
}

h1, h2, h3, h4 { color: var(--ink); font-weight: 700; line-height: 1.25; overflow-wrap: anywhere; }
/* Figures line up (tabular) in tables, tiles, axes and big figures; in running text they are
   spaced as text, so a sentence never reads like a terminal. */
p, li, dd, dt, label, legend, .hint, .panel-sub, .lede-sub { font-variant-numeric: proportional-nums; }
.num, table, .tile, .figure, .count, .chart-axis, .cap-value, .tile-num, .numbers .number, .code { font-variant-numeric: tabular-nums; }
h1 { margin: 0 0 16px; font-family: var(--font-display); font-size: 28px; line-height: 1.15; }
h2 { margin: 0 0 12px; font-size: 18px; }
h3 { margin: 20px 0 8px; font-size: 16px; }
h4 { margin: 12px 0 4px; font-size: 16px; }
p, ul, ol, dl, figure, blockquote { margin: 0; }
ul, ol { padding: 0 0 0 20px; }
li, dd { overflow-wrap: anywhere; }
td, th { overflow-wrap: break-word; }
b, strong { font-weight: 700; }
small { font-size: 14px; }
img, svg, video, canvas { max-width: 100%; }
img { height: auto; }
hr { margin: 24px 0; border: 0; border-top: 1px solid var(--rule); }
code, kbd, samp, pre { font-family: inherit; font-size: inherit; }
pre { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
[hidden] { display: none !important; }

a { color: var(--accent); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { color: var(--accent-hover); text-decoration-thickness: 2px; }

button, input, select, textarea { margin: 0; color: inherit; font: inherit; }
button { min-height: var(--tap); cursor: pointer; }
button:disabled { cursor: not-allowed; }

.sr-only {
  position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip {
  position: absolute;
  top: -80px;
  left: 8px;
  z-index: 100;
  padding: 10px 16px;
  border-radius: 8px;
  background: var(--accent-fill);
  color: var(--on-accent);
  font-weight: 700;
  text-decoration: none;
}
.skip:focus { top: 8px; color: var(--on-accent); }

/* --------------------------------------------------------------------- focus */
/* One focus style everywhere: a 2px Exchange blue ring. */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
[tabindex="-1"]:focus, [tabindex="-1"]:focus-visible { outline: none; }
input:focus-visible, select:focus-visible, textarea:focus-visible { outline-offset: 1px; border-color: var(--accent); }

/* --------------------------------------------------------------------- shell */
.shell { min-height: 100vh; min-height: 100dvh; }
.shell-main { min-width: 0; }
.sidebar { display: none; }
body.signed-out .sidebar, body.signed-out .topbar, body.signed-out .tabbar { display: none; }

@media (min-width: 960px) {
  .shell { display: grid; grid-template-columns: var(--side-w) minmax(0, 1fr); }
  body.signed-out .shell { display: block; }
  .sidebar {
    position: sticky;
    top: 0;
    display: block;
    height: 100vh;
    height: 100dvh;
    overflow-y: auto;
    border-right: 1px solid var(--rule);
    background: var(--paper);
  }
}

/* The sidebar: logo, navigation, Help, this device. */
.side-in { display: flex; flex-direction: column; min-height: 100%; padding: 20px 12px 16px; }
.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap);
  padding: 0 8px;
  color: var(--ink);
  text-decoration: none;
}
.brand:hover { color: var(--ink); }
.brand .logo-mark { display: block; flex: none; }
.logo-mark { color: var(--ink); }
.brand-name { font-family: var(--font-display); font-size: 22px; font-weight: 700; line-height: 1; white-space: nowrap; }
.side-nav { display: flex; flex: 1; flex-direction: column; margin-top: 28px; }
.side-nav ul { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
.side-nav .side-foot { margin-top: 20px; padding-top: 12px; border-top: 1px solid var(--rule); }
.side-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap);
  padding: 0 12px;
  border-radius: 8px;
  color: var(--ink);
  font-size: 16px;
  text-decoration: none;
}
.side-link .glyph { flex: none; color: var(--slate); }
.side-link:hover { background: var(--hover); color: var(--ink); }
.side-link[aria-current="page"] { background: var(--accent-tint); color: var(--accent); font-weight: 700; }
.side-link[aria-current="page"] .glyph { color: var(--accent); }
.side-link[aria-current="page"]::before {
  content: "";
  position: absolute;
  top: 10px;
  bottom: 10px;
  left: -12px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}
.side-label { min-width: 0; }
.side-link .count { margin-left: auto; }
.side-device { margin-top: 20px; padding: 14px 8px 0; border-top: 1px solid var(--rule); color: var(--slate); font-size: 13px; line-height: 1.4; }
.side-device-name { color: var(--ink); font-weight: 700; }
.side-device-conn { margin-top: 2px; }

/* The top bar: on phones the logo, the mode and Stop all rotation; on desktop the page title
   instead of the logo. Sticky, so Stop all rotation is always in reach. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  border-bottom: 1px solid var(--rule);
  background: var(--paper);
}
.topbar-in {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 60px;
  padding: env(safe-area-inset-top) var(--gutter) 0;
}
/* The phone's brand: the keypad and the wordmark beside it. The brand takes the room the mode
   badge and Stop all rotation leave, and sets the wordmark to fit it: on one line, in two short
   lines, or not at all when even that does not fit, so it is never cut. */
.brand-phone {
  display: flex;
  flex: 1 1 0;
  align-items: center;
  gap: 8px;
  min-width: 30px;
  height: var(--tap);
  padding: 0;
  overflow: hidden;
  container-type: inline-size;
}
.brand-phone .logo-mark { flex: none; }
.brand-phone .brand-name { display: none; font-size: 18px; line-height: 1; }
@container (min-width: 128px) { .brand-phone .brand-name { display: block; } }
@container (min-width: 82px) and (max-width: 127.98px) {
  .brand-phone .brand-name { display: block; width: min-content; font-size: 13px; line-height: 1.05; white-space: normal; }
}
.topbar-head { display: none; min-width: 0; }
.topbar-tools { display: flex; flex: none; align-items: center; gap: 8px; margin-left: auto; }
.page-title { margin: 0; font-family: var(--font-display); font-size: 28px; font-weight: 700; line-height: 1.15; }
.page-caption { color: var(--slate); font-family: var(--font-body); font-size: 16px; font-weight: 400; }
.back {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  color: var(--slate);
  font-size: 14px;
  text-decoration: none;
}
.back:hover { color: var(--accent); text-decoration: underline; }

@media (min-width: 960px) {
  .topbar-in { min-height: 72px; padding: 0 32px; }
  .brand-phone { display: none; }
  .topbar-head { display: block; }
  .topbar .back { min-height: 20px; margin-bottom: 2px; }
}

/* The mode, as a small block: violet "Test mode", green "Live". It explains itself when tapped. */
.mode-badge {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0;
  border: 0;
  background: none;
}
.mode-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 10px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 700;
  line-height: 20px;
  white-space: nowrap;
}
.mode-badge.test .mode-pill { background: var(--test-pale); color: var(--test); }
.mode-badge.live .mode-pill { background: var(--ok-pale); color: var(--ok); }
.mode-badge.stopped .mode-pill { background: var(--bad-pale); color: var(--bad); }
/* Some vendors differ from the app's mode: an amber edge says the badge has more to tell. */
.mode-badge.mixed .mode-pill { box-shadow: inset 0 0 0 1.5px var(--warn); }
.mode-badge .lamp { width: 12px; height: 12px; }
.mode-badge:hover .mode-pill { text-decoration: underline; text-underline-offset: 0.2em; }

/* Stop all rotation: a red outline that turns solid red on hover, on every screen. */
.btn.stop { border: 1.5px solid var(--bad); background: var(--paper); color: var(--bad); }
.btn.stop:hover { border-color: var(--bad-fill); background: var(--bad-fill); color: #FFFFFF; }
.btn.stop .glyph { width: 14px; height: 14px; }
@media (max-width: 400px) {
  .topbar-in { gap: 6px; }
  .topbar-tools { gap: 6px; }
  .btn.stop { padding: 0 10px; }
  .btn.stop .glyph { display: none; }
  .mode-pill { gap: 6px; padding: 5px 8px; }
}
/* The narrowest phones: a long badge ("Live, 3 in test mode") and Stop all rotation still fit. */
@media (max-width: 374px) {
  .topbar-in { gap: 4px; }
  .topbar-tools { gap: 4px; }
  .btn.stop { padding: 0 8px; font-size: 14px; }
  .mode-pill { gap: 5px; padding: 5px 6px; font-size: 13px; }
}

/* Bars under the top bar, on every screen: rotation stopped (red), test mode (violet), no
   connection (amber). Each holds one line of words in a p, and at most one button. */
.bar { padding: 10px var(--gutter); }
.bar-in { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; }
.bar p { margin: 0; }
.bar-stopped { background: var(--bad-fill); color: #FFFFFF; }
.bar-stopped strong { font-weight: 700; }
.bar-btn.btn { border-color: #FFFFFF; background: #FFFFFF; color: #A01F2E; }
.bar-btn.btn:hover { background: #FBE7EA; color: #8A1A27; }
.bar-offline { border-top: 1px solid var(--rule); background: var(--warn-pale); color: var(--warn-text); font-weight: 700; }
/* Test mode, the shipped default: its own violet, calm, said once for every screen. Its link
   opens the mode popover and keeps a 44 px target without making the line taller. */
.bar-test { padding-top: 8px; padding-bottom: 8px; border-top: 1px solid var(--rule); background: var(--test-pale); color: var(--test); font-size: 14px; line-height: 1.45; }
.bar-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  margin: -12px 0;
  padding: 0 2px;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 700;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  cursor: help;
}
.bar-link:hover, .bar-link[aria-expanded="true"] { text-decoration-thickness: 2px; }
.bar-test-short { display: none; }
@media (max-width: 719px) {
  .bar-test-long { display: none; }
  .bar-test-short { display: inline; }
  .bar-test { padding-top: 6px; padding-bottom: 6px; font-size: 13px; }
}
@media (min-width: 960px) { .bar { padding: 10px 32px; } .bar-test { padding-top: 8px; padding-bottom: 8px; } }

/* The connections, under the top bar on every screen (app.js), scrolling away with the page.
   Wide: a chip per connection with a lamp, a word and its age; one that is wrong is tinted and
   links to its fix. On a phone: one line, only when something is wrong, that opens the rest. */
.status-strip { border-bottom: 1px solid var(--rule); background: var(--paper); font-size: 13px; line-height: 1.4; }
.status-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px; margin: 0; padding: 7px var(--gutter); list-style: none; }
.chip { display: inline-flex; align-items: center; gap: 7px; min-width: 0; color: var(--slate); }
.chip .lamp { width: 13px; height: 13px; }
.chip-label { color: var(--ink); font-weight: 700; }
.chip.warn, .chip.bad { padding: 2px 8px; border-radius: 6px; font-weight: 700; }
.chip.warn { background: var(--warn-pale); color: var(--warn-text); }
.chip.bad { background: var(--bad-pale); color: var(--bad); }
.chip.warn .chip-label, .chip.bad .chip-label { color: inherit; }
.chip a { color: inherit; font-weight: 700; }
.status-one { display: none; }
@media (min-width: 960px) {
  .status-chips { gap: 2px 12px; padding: 5px 32px; }
  .status-chips .chip { gap: 6px; }
  .status-chips .chip .lamp { width: 13px; height: 13px; }
}
@media (max-width: 719px) {
  .status-strip:not(.has-problem) { display: none; }
  .status-strip.bad { background: var(--bad-pale); }
  .status-strip.warn { background: var(--warn-pale); }
  .status-one { display: flex; align-items: center; gap: 10px; padding: 0 var(--gutter); }
  .status-one .lamp { flex: none; }
  .status-one-text { flex: 1 1 auto; min-width: 0; padding: 10px 0; font-size: 14px; font-weight: 700; }
  .status-strip.bad .status-one-text { color: var(--bad); }
  .status-strip.warn .status-one-text { color: var(--warn-text); }
  /* Its fix keeps a 44 px target without making the line taller. */
  .status-one-text a { display: inline-flex; align-items: center; min-height: var(--tap); margin: -12px 0; color: inherit; }
  .status-toggle { flex: none; font-size: 14px; }
  .status-chips { display: none; }
  .status-strip.open .status-chips { display: grid; gap: 8px; padding: 10px var(--gutter) 12px; border-top: 1px solid var(--rule); background: var(--paper); }
  .status-strip.open .chip { align-items: baseline; }
}

/* The page. */
main {
  display: block;
  max-width: calc(var(--page-max) + 2 * var(--gutter));
  margin: 0 auto;
  padding: 16px var(--gutter) calc(var(--tab-h) + 32px + env(safe-area-inset-bottom));
}
main:focus { outline: none; }
body.signed-out main { padding-bottom: 48px; }
@media (min-width: 720px) { main { padding-top: 24px; } }
@media (min-width: 960px) {
  main { max-width: calc(var(--page-max) + 64px); margin: 0; padding: 28px 32px 64px; }
}
/* The status board starts a little higher: its first screen is full. */
@media (min-width: 1200px) { main:has(> .ov-grid) { padding-top: 16px; } }

/* The phone's bottom tabs: Overview, Rotation, Numbers, Alerts, More. */
.tabbar {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 40;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  padding-bottom: env(safe-area-inset-bottom);
  border-top: 1px solid var(--rule);
  background: var(--paper);
}
.tabbar:empty { display: none; }
@media (min-width: 960px) { .tabbar { display: none; } }
.tab {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: var(--tab-h);
  padding: 6px 4px;
  border: 0;
  background: var(--paper);
  color: var(--slate);
  font-size: 13px;
  font-weight: 700;
  line-height: 1.2;
  text-decoration: none;
}
.tab:hover { color: var(--ink); }
.tab[aria-current="page"], .tab[data-current="true"] { color: var(--accent); }
.tab[aria-current="page"]::before, .tab[data-current="true"]::before {
  content: "";
  position: absolute;
  top: -1px;
  right: 30%;
  left: 30%;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--accent);
}
.tab:focus-visible { outline-offset: -4px; }
.tab-icon { position: relative; display: inline-flex; }
.tab-icon .count { position: absolute; top: -7px; left: 13px; }
.more-scrim { position: fixed; inset: 0; z-index: 0; background: var(--scrim); }
.more-menu {
  position: absolute;
  right: 8px;
  bottom: calc(100% + 8px);
  z-index: 2;
  width: min(300px, calc(100vw - 16px));
  padding: 8px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--paper);
  box-shadow: var(--float-shadow);
}
.more-title { padding: 6px 10px 4px; color: var(--slate); font-size: 13px; }
.more-menu ul { margin: 0; padding: 0; list-style: none; }
.more-menu a {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  padding: 0 12px;
  border-radius: 8px;
  color: var(--ink);
  font-weight: 700;
  text-decoration: none;
}
.more-menu a .glyph { color: var(--slate); }
.more-menu a:hover { background: var(--hover); color: var(--ink); }
.more-menu a[aria-current="page"] { background: var(--accent-tint); color: var(--accent); }
.more-menu a[aria-current="page"] .glyph { color: var(--accent); }

/* --------------------------------------------------------- page head, sentence */
.page-head-phone { margin: 0 0 16px; }
.page-head-phone.quiet { margin: 0; }
.page-head-phone .back { min-height: var(--tap); margin: -12px 0 -4px; }
@media (min-width: 960px) { .page-head-phone { display: none; } }

/* Every page opens with one plain sentence about its state. The lamp is a status light: the
   words alone still say it. */
.lede { margin: 0 0 20px; }
.lede-text {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  max-width: 34em;
  margin: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 500;
  line-height: 1.2;
}
.lede-text .lamp { flex: none; width: 16px; height: 16px; margin-top: calc(0.6em - 8px); }
.lede-sub { max-width: 46em; margin: 8px 0 0; color: var(--slate); }
.lede-sub .whatis { vertical-align: -3px; }
.lede-hero { margin-bottom: 24px; }
.lede-hero .lede-text { font-size: 28px; line-height: 1.15; }
.lede-hero .lede-text .lamp { width: 18px; height: 18px; margin-top: calc(0.575em - 9px); }
.lede-hero .lede-sub { padding-left: 26px; }
@media (min-width: 960px) {
  .lede-hero { margin: 4px 0 28px; }
  .lede-hero .lede-text { font-size: 36px; }
  .lede-hero .lede-sub { font-size: 18px; padding-left: 28px; }
}

/* ----------------------------------------------------------------- type helpers */
.secondary { color: var(--slate); }
.small { font-size: 14px; }
.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.nowrap { white-space: nowrap; }
.break { overflow-wrap: anywhere; }
.bold { font-weight: 700; }
.text-ok { color: var(--ok); }
.text-warn { color: var(--warn-text); }
.text-bad { color: var(--bad); }
.stack > * + * { margin-top: 8px; }
.stack-lg > * + * { margin-top: 16px; }
.figure { font-family: var(--font-display); font-weight: 700; }
.prose { max-width: 42em; }
.prose p, .prose ul, .prose ol { margin: 0 0 12px; }
.prose li + li { margin-top: 4px; }
.prose > :last-child { margin-bottom: 0; }
.loading { padding: 48px 0; color: var(--slate); text-align: center; }

/* ------------------------------------------------------------------- buttons */
/* .btn alone is the secondary button. .primary (Exchange blue, one per panel or form), .danger
   (solid red: destructive confirmations), .stop (Stop all rotation). Text, or a glyph and text. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--tap);
  margin: 0;
  padding: 0 16px;
  border: 1px solid var(--rule-btn);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  touch-action: manipulation;
}
.btn:hover { border-color: var(--rule-strong); background: var(--hover); color: var(--ink); text-decoration: none; }
.btn .glyph { flex: none; width: 16px; height: 16px; }
.btn.primary { border-color: var(--accent-fill); background: var(--accent-fill); color: var(--on-accent); }
.btn.primary:hover { border-color: var(--accent-fill-hover); background: var(--accent-fill-hover); color: var(--on-accent); }
.btn.danger { border-color: var(--bad-fill); background: var(--bad-fill); color: #FFFFFF; }
.btn.danger:hover { border-color: var(--bad-fill-hover); background: var(--bad-fill-hover); color: #FFFFFF; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }

/* A button that looks like a link: "Hide this tip", "Skip tour", "Show all 12". */
.btn-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-weight: 700;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.btn-link:hover { color: var(--accent-hover); text-decoration: underline; text-underline-offset: 0.18em; }
.btn-link:disabled { color: var(--slate); cursor: not-allowed; text-decoration: none; }

/* A row of buttons and links. .fill shares the width on phones. */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 16px; }
.actions:first-child { margin-top: 0; }
@media (max-width: 719px) {
  .actions.fill { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
  .actions.stack-phone { flex-direction: column; align-items: stretch; }
}

/* One choice of a few: the chart's 1 h, 3 h, 24 h. */
.seg { display: inline-flex; overflow: hidden; border: 1px solid var(--rule-btn); border-radius: 8px; background: var(--paper); }
.seg button {
  min-width: 52px;
  min-height: var(--tap);
  padding: 0 12px;
  border: 0;
  background: none;
  color: var(--slate);
  font-size: 14px;
  font-weight: 700;
}
.seg button + button { border-left: 1px solid var(--rule-btn); }
.seg button:hover { background: var(--hover); color: var(--ink); }
.seg button[aria-pressed="true"] { background: var(--accent-tint); color: var(--accent); }
.seg button:focus-visible { outline-offset: -3px; }

/* Tabs that are links: Alerts' Open, History, Rules. */
.tabs { display: flex; gap: 4px; margin: 0 0 20px; overflow-x: auto; border-bottom: 1px solid var(--rule); }
.tabs a {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 8px;
  min-height: var(--tap);
  margin-bottom: -1px;
  padding: 0 14px;
  border-bottom: 3px solid transparent;
  color: var(--slate);
  font-weight: 700;
  text-decoration: none;
}
.tabs a:hover { color: var(--ink); }
.tabs a[aria-current="page"] { border-bottom-color: var(--accent); color: var(--accent); }
.tabs a:focus-visible { outline-offset: -3px; }

/* "What is this?": the help glyph after a term. The visible mark is small; the button itself is
   44px tall, with margins that keep the line height. */
.whatis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  margin: -14px 0 -14px 2px;
  padding: 0 3px;
  border: 0;
  background: none;
  color: var(--slate);
  vertical-align: middle;
  cursor: help;
}
.whatis:hover, .whatis[aria-expanded="true"] { color: var(--accent); }
.whatis .glyph { display: block; }

/* ------------------------------------------------------------ badges and lamps */
/* A status in a pale block. Tones: ok, warn, bad, test, off, info. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--off-pale);
  color: var(--slate);
  font-size: 13px;
  font-weight: 700;
  line-height: 20px;
  white-space: nowrap;
}
.badge.ok { background: var(--ok-pale); color: var(--ok); }
.badge.warn { background: var(--warn-pale); color: var(--warn-text); }
.badge.bad { background: var(--bad-pale); color: var(--bad); }
.badge.test { background: var(--test-pale); color: var(--test); }
.badge.info { background: var(--accent-tint); color: var(--accent); }

/* The count badge, the only fully round thing: Alerts (2). */
.count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--bad-fill);
  color: #FFFFFF;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
}
.count.zero { background: var(--off-pale); color: var(--slate); }

/* A status light, like the ones on a rack. */
/* Status icons, not dots: a tick (fine), a triangle (a warning), an octagon (a problem), a flask
   (test mode) and a dash (off), each in its state's colour. The shape is a mask, so the colour
   is the background and the state's class sets both. */
.lamp {
  display: inline-block; flex: none; width: 14px; height: 14px; background: var(--rule-strong);
  -webkit-mask: var(--lamp-off) center / contain no-repeat; mask: var(--lamp-off) center / contain no-repeat;
}
.lamp.ok { background: var(--ok); -webkit-mask-image: var(--lamp-ok); mask-image: var(--lamp-ok); }
.lamp.warn { background: var(--warn); -webkit-mask-image: var(--lamp-warn); mask-image: var(--lamp-warn); }
.lamp.bad { background: var(--bad); -webkit-mask-image: var(--lamp-bad); mask-image: var(--lamp-bad); }
.lamp.test { background: var(--test); -webkit-mask-image: var(--lamp-test); mask-image: var(--lamp-test); }
.lamp.off { background: var(--rule-strong); }

/* ------------------------------------------------------------- not saved bar */
/* Changes not saved yet (app.js for Settings and a vendor's page, alerts.js for the alert form):
   what they are, then Save and Undo changes, in one bar fixed to the bottom of the window, above
   the phone's tabs and right of the sidebar. The page gets room under its last line, a field
   reached with Tab stays clear of it, and the toasts move up over it. */
.form-bar {
  position: fixed;
  right: 0;
  bottom: calc(var(--tab-h) + env(safe-area-inset-bottom));
  left: 0;
  z-index: 35;
  border-top: 1px solid var(--rule);
  background: var(--paper);
}
.form-bar-in {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 16px;
  max-width: calc(var(--page-max) + 2 * var(--gutter));
  margin: 0 auto;
  padding: 8px var(--gutter);
}
.form-bar-text { display: flex; align-items: center; gap: 8px; min-width: 0; margin: 0; font-size: 14px; font-weight: 700; line-height: 1.35; }
.form-bar-text .lamp { flex: none; }
.form-bar-actions { display: flex; flex: 1 0 auto; justify-content: flex-end; gap: 8px; }
.form-bar-actions .btn { flex: none; }
@media (max-width: 719px) {
  .form-bar-actions .btn { flex: 1 1 0; }
}
@media (min-width: 960px) {
  .form-bar { bottom: 0; left: var(--side-w); }
  .form-bar-in { max-width: calc(var(--page-max) + 64px); margin: 0; padding: 10px 32px; }
}
html:has(.form-bar) { scroll-padding-bottom: calc(var(--tab-h) + 104px + env(safe-area-inset-bottom)); }
body:has(.form-bar) main { padding-bottom: calc(var(--tab-h) + 120px + env(safe-area-inset-bottom)); }
body:has(.form-bar) .toasts { bottom: calc(var(--tab-h) + 96px + env(safe-area-inset-bottom)); }
@media (min-width: 960px) {
  html:has(.form-bar) { scroll-padding-bottom: 88px; }
  body:has(.form-bar) main { padding-bottom: 120px; }
  body:has(.form-bar) .toasts { bottom: 88px; }
}

/* -------------------------------------------------------------------- panels */
.panel {
  min-width: 0;
  padding: 16px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--paper);
}
@media (min-width: 720px) { .panel { padding: 20px; } }
.panel + .panel { margin-top: 16px; }
@media (min-width: 720px) { .panel + .panel { margin-top: 24px; } }
.panel-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px 16px; margin: 0 0 16px; }
.panel-titles { flex: 1 1 240px; min-width: 0; }
.panel-head h2 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 18px; line-height: 1.3; }
.panel-sub { margin: 2px 0 0; color: var(--slate); font-size: 14px; }
.panel-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* What is missing and what to do next. */
.empty-state { display: flex; align-items: flex-start; gap: 12px; padding: 16px; border: 1px solid var(--rule); border-radius: 8px; background: var(--mist); }
.empty-icon { display: inline-flex; flex: none; color: var(--slate); }
.empty-body { min-width: 0; }
.empty-title { margin: 0; font-weight: 700; }
.empty-body > p + p { margin-top: 4px; color: var(--slate); }
.empty-body .actions { margin-top: 12px; }

/* The tip at the top of a screen, until "Hide this tip". */
.tip {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 0 10px;
  margin: 0 0 20px;
  padding: 10px 14px 2px;
  border-radius: 8px;
  background: var(--accent-tint);
}
.tip-mark { display: inline-flex; padding-top: 3px; color: var(--accent); }
.tip p { max-width: 44em; padding-top: 1px; }
.tip .btn-link { grid-column: 2; justify-self: start; }

/* A problem or a result: a bold first line, what to do, at most one link or button. */
.alert { margin: 0 0 16px; padding: 12px 16px; border-radius: 8px; background: var(--off-pale); }
.alert:last-child { margin-bottom: 0; }
.alert.ok { background: var(--ok-pale); }
.alert.warn { background: var(--warn-pale); }
.alert.bad { background: var(--bad-pale); }
.alert.info { background: var(--accent-tint); }
.alert-title { font-weight: 700; }
.alert p + p { margin-top: 4px; }
.alert ul { margin-top: 4px; }
.alert .alert-action { margin-top: 10px; }

/* --------------------------------------------------------------------- forms */
.field { min-width: 0; margin: 0 0 20px; }
.field:last-child { margin-bottom: 0; }
.field > label:not(.check):not(.radio), .label { display: block; margin: 0 0 6px; font-weight: 700; }
fieldset { min-width: 0; margin: 0 0 20px; padding: 0; border: 0; }
legend { margin: 0 0 8px; padding: 0; font-weight: 700; }
.hint { display: block; margin: 0 0 8px; color: var(--slate); font-size: 14px; }
input + .hint, select + .hint, textarea + .hint, .check + .hint, .radio + .hint,
.input-unit + .hint, .choices + .hint { margin: 8px 0 0; }

input[type="text"], input[type="number"], input[type="search"], input[type="password"],
input[type="email"], input[type="url"], input[type="tel"], input[type="date"], input[type="time"], input:not([type]),
select, textarea {
  display: block;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  min-height: var(--tap);
  padding: 9px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  line-height: 1.3;
}
input[type="text"], input[type="number"], input[type="search"], input[type="password"],
input[type="email"], input[type="url"], input[type="tel"], input:not([type]), textarea {
  -webkit-appearance: none;
  appearance: none;
}
textarea { min-height: 160px; line-height: 1.45; resize: vertical; }
textarea.short { min-height: 96px; }
input::placeholder, textarea::placeholder { color: var(--slate); opacity: 1; }
input:disabled, select:disabled, textarea:disabled { opacity: 0.55; cursor: not-allowed; }
.w-number { max-width: 9em; }
.w-short { max-width: 16em; }
.w-medium { max-width: 24em; }

.input-unit { display: flex; align-items: center; gap: 10px; }
.input-unit > input, .input-unit > select { flex: 0 1 9em; min-width: 0; }
.input-unit > span { flex: none; color: var(--slate); }

.choices { display: grid; gap: 2px; }
.check, .radio { display: flex; align-items: flex-start; gap: 12px; min-height: var(--tap); padding: 10px 0; cursor: pointer; }
.check input, .radio input { flex: none; width: 20px; height: 20px; margin: 2px 0 0; accent-color: var(--accent); cursor: pointer; }
.check > span, .radio > span { min-width: 0; }
.check small, .radio small { display: block; color: var(--slate); }

.field.has-error { padding: 10px 12px; border: 1px solid var(--bad); border-radius: 6px; background: var(--bad-pale); }
.field-error { display: block; margin: 0 0 8px; color: var(--bad); font-weight: 700; }
.field.has-error input, .field.has-error select, .field.has-error textarea { border-color: var(--bad); }

.count-line { margin: 8px 0 0; color: var(--slate); }
.count-line b { color: var(--ink); }

.code { font-family: var(--font-display); font-size: 1.25em; font-weight: 700; letter-spacing: 0.02em; overflow-wrap: anywhere; }
.code-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 12px 0; padding: 0; list-style: none; }
.code-list > li {
  padding: 10px 8px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--mist);
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 700;
  text-align: center;
}

/* -------------------------------------------------------------------- tables */
/* Lines between rows, no stripes. Headers in sentence case, numbers right-aligned. */
.table-wrap { margin: 0; overflow-x: auto; }
.table { width: 100%; margin: 0; border-collapse: collapse; }
.table caption { margin: 0 0 8px; font-weight: 700; text-align: left; }
.table th, .table td { padding: 12px 12px 12px 0; border-bottom: 1px solid var(--rule); text-align: left; vertical-align: middle; }
.table th:last-child, .table td:last-child { padding-right: 0; }
.table thead th { padding-top: 0; padding-bottom: 8px; color: var(--slate); font-size: 13px; font-weight: 700; white-space: nowrap; }
.table tbody tr:last-child > * { border-bottom: 0; }
.table .num, .table td.num, .table th.num { text-align: right; }

/* --------------------------------------------------------------------- lists */
.plain-list { margin: 0; padding: 0; list-style: none; }
.bullets { padding-left: 20px; list-style: disc; }
.bullets li + li { margin-top: 4px; }

/* Rows with a line between them. A row with a state gets a coloured bar at its left edge. */
.rows { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--rule); }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; padding: 12px 0; border-bottom: 1px solid var(--rule); }
.row-main { display: grid; flex: 1 1 220px; gap: 2px; min-width: 0; }
.row-main b, .row-main strong { overflow-wrap: anywhere; }
.row-side { display: flex; flex: none; flex-wrap: wrap; align-items: center; gap: 8px; }

/* Lines of "Name: value", one per line. */
.lines { margin: 0; padding: 0; list-style: none; }
.lines > li { padding: 10px 0; border-bottom: 1px solid var(--rule); }
.lines > li:last-child { border-bottom: 0; }

/* The result of a command, under its buttons: "Rotate now: done (verified), batch 2". */
.cmd-line { margin-top: 8px; color: var(--slate); font-size: 14px; }
.cmd-line.ok { color: var(--ok); font-weight: 700; }
.cmd-line.bad { color: var(--bad); font-weight: 700; }
.cmd-line.test { color: var(--test); font-weight: 700; }
.usage, .pool-line { color: var(--slate); }

/* Phone numbers with their counts. */
.numbers { display: grid; margin: 0; padding: 0; list-style: none; }
.numbers > li { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 0 12px; padding: 7px 0; border-bottom: 1px solid var(--rule); }
.numbers > li:last-child { border-bottom: 0; }
.numbers .number { font-family: var(--font-display); font-size: 17px; font-weight: 500; }
.numbers .facts { color: var(--slate); text-align: right; }
.numbers li.used .number { color: var(--slate); text-decoration: line-through; }
.numbers li.used .facts { color: var(--bad); font-weight: 700; }
.numbers .current .facts { color: var(--ink); font-weight: 700; }
@media (min-width: 720px) {
  .numbers { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 32px; }
  .numbers > li:nth-last-child(2):nth-child(odd) { border-bottom: 0; }
}

/* Key and value rows. Phones: key above value. Wider: two columns, keys 30%. */
.summary { margin: 0 0 20px; }
.summary-row { padding: 10px 0; border-bottom: 1px solid var(--rule); }
.summary dt { margin: 0 0 2px; color: var(--slate); font-size: 14px; font-weight: 700; }
.summary dd { margin: 0; }
.summary dd + dd { margin-top: 4px; }
@media (min-width: 720px) {
  .summary { display: table; width: 100%; border-collapse: collapse; table-layout: fixed; }
  .summary-row { display: table-row; padding: 0; }
  .summary dt, .summary dd { display: table-cell; padding: 11px 20px 11px 0; border-bottom: 1px solid var(--rule); vertical-align: top; }
  .summary dt { width: 30%; margin: 0; }
  .summary dd + dd { margin: 0; }
  .summary .summary-action { width: 20%; padding-right: 0; text-align: right; }
}

/* <details class="disclosure"><summary>More settings</summary> (the content) </details> */
summary { min-height: var(--tap); cursor: pointer; }
.disclosure { margin: 4px 0 0; }
.disclosure > summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: var(--tap);
  color: var(--accent);
  font-weight: 700;
  list-style: none;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  margin: 0 2px 0 1px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
}
.disclosure[open] > summary::before { transform: rotate(45deg); margin-top: -4px; }
.disclosure > summary:hover { color: var(--accent-hover); }
.disclosure > :not(summary) { margin: 4px 0 8px; padding: 4px 0 4px 16px; }

/* ------------------------------------------------------------- number strip */
/* The caller IDs in use as tiles, 12 digits grouped "44 2035 043219". A thin line in each tile
   shows how much of its attempts limit is used. The strip rolls once when a rotation is
   confirmed: the only automatic motion in the app. */
.strip-wrap { min-width: 0; }
.strip { display: grid; grid-template-columns: repeat(auto-fill, minmax(124px, 1fr)); gap: 6px; margin: 0; padding: 0; list-style: none; }
.tile {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 38px;
  padding: 6px 8px 8px;
  overflow: hidden;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--mist);
}
.tile-num {
  display: block;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.tile-use { position: absolute; bottom: 0; left: 0; width: var(--pct, 0%); height: 3px; background: var(--rule-strong); }
.tile.warn .tile-use { background: var(--warn); }
.tile.bad { background: var(--paper); }
.tile.bad .tile-num { color: var(--slate); text-decoration: line-through; text-decoration-thickness: 1px; }
.tile.bad .tile-use { background: var(--bad); }
.strip-none { color: var(--slate); }
.strip-more { display: none; }
@media (max-width: 719px) {
  .strip-wrap:not(.open) .tile-extra { display: none; }
  .strip-more { display: inline-flex; font-size: 14px; }
}
@keyframes roll-a { from { transform: translateY(80%); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes roll-b { from { transform: translateY(80%); opacity: 0; } to { transform: none; opacity: 1; } }
.strip.roll-a .tile-num { animation: roll-a 250ms cubic-bezier(0.2, 0.7, 0.2, 1) both; }
.strip.roll-b .tile-num { animation: roll-b 250ms cubic-bezier(0.2, 0.7, 0.2, 1) both; }

/* -------------------------------------------------------------------- charts */
/* Series colours: one per customer, in a fixed order, never the status colours. */
.s1 { --c: var(--series-1); }
.s2 { --c: var(--series-2); }
.s3 { --c: var(--series-3); }
.s4 { --c: var(--series-4); }
.s5 { --c: var(--series-5); }
.s6 { --c: var(--series-6); }

.chart { position: relative; min-width: 0; border-radius: 8px; }
.chart:focus-visible { outline-offset: 4px; }
.chart-svg { display: block; width: 100%; height: auto; overflow: visible; }
.chart-grid { stroke: var(--rule); stroke-width: 1; }
.chart-base { stroke: var(--rule-strong); stroke-width: 1; }
.chart-axis { fill: var(--slate); font-family: var(--font-display); font-size: 13px; font-weight: 500; }
.chart-line { fill: none; stroke: var(--c); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.chart-line.strong { stroke-width: 3; }
.chart-end { fill: var(--c); stroke: var(--paper); stroke-width: 2; }
.chart-level { stroke: var(--c); stroke-width: 1.5; stroke-dasharray: 7 5; }
/* Each level's words sit in the gutter right of the plot, joined to the level by the same dash
   in the series colour; the words are ink for contrast, its figure slate. */
.chart-level-lead { fill: none; stroke: var(--c); stroke-width: 1.5; stroke-dasharray: 3 2; }
.chart-level-label { fill: var(--ink); font-family: var(--font-body); font-size: 12px; font-weight: 700; }
.chart-level-sub { fill: var(--slate); font-weight: 400; }
.chart-cross circle { fill: var(--bad-pale); fill-opacity: 0.6; stroke: var(--bad); stroke-width: 2.5; }
.chart-tick line { stroke: var(--slate); stroke-width: 2; stroke-linecap: round; }
.chart-tick:hover line { stroke: var(--ink); }
.chart-empty {
  position: absolute;
  inset: 0 0 32px 40px;
  display: grid;
  place-items: center;
  margin: 0;
  padding: 16px;
  color: var(--slate);
  text-align: center;
}
.chart-hover { position: absolute; inset: 0; pointer-events: none; }
.chart-cursor { position: absolute; width: 1px; background: var(--rule-strong); }
.chart-dot { position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border: 2px solid var(--paper); border-radius: 50%; background: var(--c); }
.chart-readout {
  position: absolute;
  min-width: 170px;
  padding: 8px 10px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--paper);
  box-shadow: var(--float-shadow);
  font-size: 13px;
}
.chart-readout p { display: flex; align-items: center; gap: 8px; margin: 0; }
.chart-readout p + p { margin-top: 2px; }
.chart-readout b { margin-left: auto; padding-left: 12px; font-family: var(--font-display); font-size: 16px; font-weight: 700; }
.chart-readout-time { color: var(--slate); font-weight: 700; }

.legend { display: flex; flex-wrap: wrap; gap: 4px 20px; margin: 0 0 12px; font-size: 14px; }
.legend ul { display: flex; flex-wrap: wrap; gap: 4px 20px; margin: 0; padding: 0; list-style: none; }
.legend li { display: inline-flex; align-items: center; gap: 8px; min-height: 24px; }
.legend-name { font-weight: 700; }
.legend-value { color: var(--slate); }
.legend-keys { color: var(--slate); }
.legend-phone { display: none !important; }
.legend .legend-more { display: none; }
@media (min-width: 1200px) { .legend-series .legend-value { display: none; } }
/* Phones: the plot first, then a short legend under it, one line per customer, those with an
   alert open first and at most two, then how many more. */
.hero-body { display: flex; flex-direction: column; }
@media (max-width: 719px) {
  .hero-body > .legend { order: 2; margin: 10px 0 0; }
  .hero-body > .chart { order: 1; }
  .hero-body > .hero-foot { order: 3; }
  .legend { display: block; }
  .legend .legend-series { display: grid; gap: 2px; }
  .legend-series li { display: flex; align-items: center; gap: 8px; min-width: 0; }
  .legend-series .legend-name { min-width: 0; overflow-wrap: anywhere; }
  .legend-series .legend-value { margin-left: auto; font-size: 13px; white-space: nowrap; }
  .legend .legend-extra, .legend .legend-wide { display: none !important; }
  .legend .legend-phone { display: inline-flex !important; }
  .legend .legend-keys { gap: 0 16px; margin-top: 6px; }
}
.swatch { display: inline-block; flex: none; width: 14px; height: 4px; border-radius: 2px; background: var(--c, var(--slate)); }
.swatch-none { background: none; border: 1px solid var(--rule-strong); height: 6px; }
.swatch-dash { display: inline-block; width: 18px; height: 0; border-top: 2px dashed var(--slate); }
.swatch-tick { display: inline-block; width: 2px; height: 10px; border-radius: 1px; background: var(--slate); }

.spark { display: block; width: 112px; height: 32px; overflow: visible; }
.spark-line { fill: none; stroke: var(--c); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.spark-level { stroke: var(--c); stroke-width: 1; stroke-dasharray: 3 3; }
.spark-end { fill: var(--c); }
.spark-empty { display: block; width: 112px; height: 32px; border-bottom: 1px dashed var(--rule-strong); }

/* A thin bar for how much of a limit is used, with the alert level marked. */
.meter { position: relative; display: block; height: 6px; border-radius: 3px; background: var(--off-pale); }
.meter-fill { position: absolute; top: 0; bottom: 0; left: 0; width: var(--pct, 0%); border-radius: 3px; background: var(--slate); }
.meter.warn .meter-fill { background: var(--warn); }
.meter.bad .meter-fill { background: var(--bad); }
.meter-mark { position: absolute; top: -4px; bottom: -4px; left: var(--mark, 100%); width: 2px; margin-left: -1px; background: repeating-linear-gradient(to bottom, var(--slate) 0 3px, transparent 3px 5px); }

/* ------------------------------------------------------------------ Overview */
/* One column on phones and tablets, in the order a newcomer reads it: Right now, Needs
   attention, then (on a phone) the chart, Customers now and the vendors. At 1200 px it is a
   status board on three columns:
     Right now: Calls | Numbers | Connections, side by side
     Needs attention, one line of problems per card
     Customers now (two columns)          | Vendors and trunk groups
     Calls per minute (all three, low) */
.ov-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
.ov-grid > .panel + .panel { margin-top: 0; }
@media (max-width: 719px) {
  .ov-grid > .now { order: 1; }
  .ov-grid > .ov-attention { order: 2; }
  .ov-grid > .hero { order: 3; }
  .ov-grid > .ov-customers { order: 4; }
  .ov-grid > .ov-vendors { order: 5; }
}
@media (min-width: 720px) { .ov-grid { gap: 24px; } }
@media (min-width: 1200px) {
  .ov-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-areas:
      "now now now"
      "attention attention attention"
      "customers customers vendors"
      "hero hero hero";
    align-items: stretch;
  }
  .ov-grid > .now { grid-area: now; }
  .ov-grid > .ov-attention { grid-area: attention; }
  .ov-grid > .ov-customers { grid-area: customers; }
  .ov-grid > .ov-vendors { grid-area: vendors; }
  .ov-grid > .hero { grid-area: hero; }
}

/* What the app is for, on the first visit, the same on a phone and on a desktop. Its three
   lines carry the lamps the screens use: green, red, and the red edge of Stop all rotation. */
.welcome { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px 20px; margin: 0 0 16px; }
@media (min-width: 720px) { .welcome { margin-bottom: 24px; } }
.welcome-mark { display: none; }
.welcome-mark .logo-mark { display: block; }
@media (min-width: 600px) {
  .welcome { grid-template-columns: auto minmax(0, 1fr); }
  .welcome-mark { display: block; }
}
.welcome h2 { margin: 0; font-size: 18px; font-weight: 700; }
.welcome-lead { max-width: 40em; margin: 6px 0 14px; }
.welcome-keys { display: grid; gap: 8px; margin: 0 0 18px; padding: 0; list-style: none; }
.welcome-keys li { display: flex; align-items: baseline; gap: 12px; font-weight: 700; }
.welcome-keys .lamp, .welcome-stop { flex: none; width: 14px; height: 14px; transform: translateY(1px); }
.welcome-stop { display: inline-block; border: 2px solid var(--bad); border-radius: 3px; }
@media (min-width: 1100px) {
  .welcome-keys { grid-auto-flow: column; justify-content: start; gap: 8px 36px; }
}

/* Right now: three rows like the lamps on a rack, each a link to the screen with more. The
   sentence is set like the page sentence it replaces; the word before it names the row. */
.now { padding: 0; overflow: hidden; container-type: inline-size; }
/* Its heading, and the clock the page's times are on, said once ("Times are UK time."). */
.now-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0 12px; padding: 14px 16px 10px; }
.now-title { margin: 0; font-size: 18px; }
.now-times { margin: 0; color: var(--slate); font-size: 13px; }
.now-list { margin: 0; padding: 0; list-style: none; }
.now-row {
  display: grid;
  grid-template-columns: 12px minmax(0, 1fr) 10px;
  grid-template-areas: "lamp label go" ". text go";
  align-items: center;
  gap: 2px 12px;
  min-height: 64px;
  padding: 10px 18px 12px 16px;
  border-top: 1px solid var(--rule);
  color: var(--ink);
  text-decoration: none;
}
.now-row:hover { background: var(--hover); color: var(--ink); text-decoration: none; }
.now-row:hover .now-text { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
.now-row:focus-visible { outline-offset: -3px; }
.now-row .lamp { grid-area: lamp; width: 15px; height: 15px; }
.now-label { grid-area: label; color: var(--slate); font-size: 13px; font-weight: 700; line-height: 1.3; }
.now-row.ok .now-label { color: var(--ok); }
.now-row.warn .now-label { color: var(--warn-text); }
.now-row.bad .now-label { color: var(--bad); }
.now-row.test .now-label { color: var(--test); }
.now-text { grid-area: text; min-width: 0; font-family: var(--font-display); font-size: 20px; font-weight: 500; line-height: 1.2; font-variant-numeric: proportional-nums; }
.now-go {
  grid-area: go;
  width: 9px;
  height: 9px;
  border-top: 2px solid var(--slate);
  border-right: 2px solid var(--slate);
  transform: rotate(45deg);
}
@media (min-width: 720px) {
  .now-head { padding: 18px 20px 12px; }
  .now-row { padding: 12px 22px 14px 20px; }
}
@media (max-width: 719px) {
  .now-head { padding-top: 12px; padding-bottom: 8px; }
  .now-row { padding-top: 8px; padding-bottom: 10px; }
  .now-text { font-size: 18px; }
}
/* Wide enough: the word in a column of its own and the sentence beside it, one row a line. */
@container (min-width: 560px) {
  .now-row { grid-template-columns: 12px 104px minmax(0, 1fr) 10px; grid-template-areas: "lamp label text go"; min-height: 58px; }
  .now-label { font-size: 14px; }
  .now-text { font-size: 22px; }
}
/* The status board: the three side by side, like the lamps along a rack, each its own cell. */
@container (min-width: 900px) {
  .now-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .now-list > li { display: flex; min-width: 0; }
  .now-list > li + li .now-row { border-left: 1px solid var(--rule); }
  .now-row {
    flex: 1 1 auto;
    grid-template-columns: 12px minmax(0, 1fr) 10px;
    grid-template-areas: "lamp label go" ". text go";
    align-content: start;
    min-height: 0;
    padding: 8px 18px 10px 20px;
  }
  .now-text { font-size: 18px; }
  .now-head { padding-top: 8px; padding-bottom: 6px; }
}
/* The status board: the heading, and the clock line, in a column of their own at the left of
   the three tiles, so Right now is one low band. */
@media (min-width: 1200px) {
  .ov-grid > .now { display: grid; grid-template-columns: 128px minmax(0, 1fr); align-items: stretch; }
  .ov-grid > .now .now-head { flex-direction: column; flex-wrap: nowrap; justify-content: center; gap: 2px; padding: 10px 0 10px 20px; }
  .ov-grid > .now .now-list { padding: 10px 10px 10px 0; }
  .ov-grid > .now .now-row { grid-template-areas: "lamp label go" "text text go"; gap: 2px 10px; padding: 6px 12px 8px 14px; }
}

/* Needs attention: one card per thing that is wrong, as on Alerts: a mark and a 3 px bar in its
   tone, who it is about and when it started, one line per problem, what to do, then the fix and
   Stop reminders, beside the words when the panel is wide enough and under them when not. */
.ov-attention { container-type: inline-size; }
.attn-list { border-top: 0; }
.attn.row {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  grid-template-areas: "mark body" ". action" ". result";
  align-items: start;
  gap: 0 12px;
  padding-top: 12px;
  padding-bottom: 14px;
}
.attn.row:first-child { padding-top: 4px; }
/* Overview's cards are tinted boxes (css/contrast.css): the first has the same room as the rest. */
.ov-attention .attn.row:first-child { padding-top: 12px; }
@media (min-width: 1200px) and (pointer: fine) { .ov-attention .attn.row:first-child { padding-top: 8px; } }
.attn-mark { grid-area: mark; display: inline-flex; padding-top: 2px; color: var(--slate); }
.attn.bad .attn-mark { color: var(--bad); }
.attn.warn .attn-mark { color: var(--warn-text); }
.attn-body { grid-area: body; min-width: 0; }
.attn-title { margin: 0; font-weight: 700; line-height: 1.4; }
.attn-title a { color: var(--ink); text-decoration: none; }
.attn-title a:hover { color: var(--accent); text-decoration: underline; }
.attn.off .attn-title, .attn.off .attn-title a { color: var(--slate); }
.attn-when { margin-left: 6px; color: var(--slate); font-size: 13px; font-weight: 400; white-space: nowrap; }
.attn-lines { margin: 2px 0 0; padding: 0; list-style: none; }
.attn-lines li { line-height: 1.45; }
.attn-todo { max-width: 46em; margin: 6px 0 0; color: var(--slate); font-size: 14px; }
.attn-todo b { color: var(--ink); }
.attn-action { grid-area: action; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
.attn-action .btn { padding: 0 12px; }
.attn-result { grid-area: result; min-width: 0; }
.attn-result .cmd-line { margin-top: 8px; }
@container (min-width: 560px) {
  .attn.row { grid-template-columns: 18px minmax(0, 1fr) auto; grid-template-areas: "mark body action" ". result result"; gap: 0 16px; }
  .attn-action { flex-direction: column; align-items: stretch; margin-top: 0; }
  .attn-lines li { display: inline; }
  .attn-lines li:not(:last-child)::after { content: ". "; }
}
/* The status board: one low row per problem. Who and when, then its lines, beside the fix and
   Stop reminders (side by side); what to do under them, across the row. */
@container (min-width: 900px) {
  .ov-attention .attn.row { grid-template-areas: "mark what action" ". todo todo" ". result result"; align-items: center; gap: 0 20px; }
  .ov-attention .attn-mark { align-self: start; }
  .ov-attention .attn-body { display: contents; }
  .ov-attention .attn-what { grid-area: what; min-width: 0; }
  .ov-attention .attn-title, .ov-attention .attn-lines { display: inline; }
  .ov-attention .attn-lines { margin: 0 0 0 12px; }
  .ov-attention .attn-todo { grid-area: todo; max-width: none; margin-top: 2px; }
  .ov-attention .attn-action { flex-direction: row; flex-wrap: nowrap; align-items: center; }
}
@media (min-width: 1200px) and (pointer: fine) {
  .attn-action { gap: 6px; }
  .attn-action .btn { min-height: 32px; font-size: 13px; }
  .attn.row { padding-top: 8px; padding-bottom: 8px; }
  .ov-attention .panel-head { margin-bottom: 8px; }
}
/* The status board: each problem one low row, its who, when and what on one line where they fit
   beside the buttons, so Customers now starts on the first screen. */
@media (min-width: 1200px) {
  .ov-grid > .ov-attention { padding-top: 16px; padding-bottom: 16px; }
  .ov-attention .attn-list > .attn:last-child { margin-bottom: 0; }
  .ov-attention .attn-title, .ov-attention .attn-lines li { font-size: 15px; }
  .ov-attention .attn.row { column-gap: 14px; }
  .ov-attention .attn-lines { margin-left: 8px; }
  .ov-attention .attn-action .btn { padding: 0 10px; }
}
/* Phones: the problem cards keep more of the width for their words, so the page with three
   problems open stays under three screens. */
@media (max-width: 719px) {
  .ov-grid > .ov-attention { padding-right: 12px; padding-left: 12px; }
  .ov-attention .attn.row { padding-right: 8px; padding-left: 10px; column-gap: 8px; }
}
.attn-more, .attn-off { margin-top: 12px; }
.attn-off { font-size: 14px; font-weight: 700; }

/* Customers now: a table wide, one block per customer on a phone. A figure past its alert level
   is red. The state badge sits under the name; the alert level under the sparkline it is drawn
   on (not on the status board, where the dashes say it). */
.cust-table thead th { white-space: normal; vertical-align: bottom; }
.cust-table thead th .whatis { margin-top: -16px; margin-bottom: -12px; }
.cust-table th[scope="row"] { font-weight: 700; }
.cust-who { display: flex; align-items: center; gap: 10px; }
.cust-who a { color: var(--ink); text-decoration: none; }
.cust-who a:hover { color: var(--accent); text-decoration: underline; }
.cust-state { display: block; margin: 4px 0 0 24px; }
.cust-state:empty { display: none; }
.cust-table td.num b { font-family: var(--font-display); font-size: 19px; font-weight: 700; }
.cust-table td.cust-change b { font-family: var(--font-body); font-size: 14px; white-space: nowrap; }
.cell-unit { display: none; }
.cust-table .cust-spark { width: 140px; min-width: 140px; text-align: right; }
.cust-spark .spark { width: 120px; max-width: none; margin-left: auto; }
.cust-level { display: block; margin: 4px 0 0 auto; color: var(--slate); font-size: 12px; line-height: 1.3; text-align: right; }
.cust-quiet-line { display: none; }
@media (min-width: 1200px) {
  /* The status board: the title and its one line side by side, and the column heads on at most
     two lines, so the first customers are on the first screen. */
  .ov-grid > .ov-customers { padding-top: 16px; }
  .ov-customers .panel-head { margin-bottom: 8px; }
  .ov-customers .panel-titles { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; }
  .ov-customers .panel-sub { margin: 0; }
  .ov-customers .cust-table thead th { padding-top: 6px; padding-bottom: 6px; }
  .ov-customers .cust-table thead th.num { min-width: 66px; }
  .ov-customers .table th, .ov-customers .table td { padding: 7px 8px 7px 0; }
  .ov-customers .table th:last-child, .ov-customers .table td:last-child { padding-right: 0; }
  .ov-customers .table thead th { padding-top: 0; padding-bottom: 8px; }
  .cust-table .cust-spark { width: 92px; min-width: 92px; }
  .cust-spark .spark { width: 88px; }
  .cust-level { display: none; }
}
@media (max-width: 719px) {
  .cust-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .cust-table, .cust-table tbody { display: block; }
  .cust-table tr { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 14px; padding: 14px 0; }
  .cust-table tr + tr { border-top: 1px solid var(--rule); }
  .cust-table th, .cust-table td { display: block; padding: 0; border: 0; text-align: left; }
  .cust-table .cust-name { display: flex; flex: 1 1 100%; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 4px; }
  .cust-state { margin: 0; }
  .cust-table td.num { flex: none; white-space: nowrap; }
  .cust-table td.num b { font-size: 18px; }
  .cust-table td.cust-change b { font-size: 15px; }
  .cell-unit { display: inline; color: var(--slate); font-size: 13px; }
  .cust-table td.cust-calls { display: none; }
  .cust-table .cust-spark { display: flex; flex: 1 1 100%; align-items: center; gap: 12px; width: auto; min-width: 0; margin-top: 8px; text-align: left; }
  .cust-spark .spark { flex: none; margin: 0; }
  .cust-level { max-width: none; margin: 0; text-align: left; }
  /* Only the customers with an open alert, until Show all. */
  .ov-customers:not(.show-all) .cust-quiet { display: none; }
  .cust-table tr:first-child { padding-top: 0; }
  .cust-quiet-line { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; padding-top: 12px; border-top: 1px solid var(--rule); }
  .cust-quiet-line p { flex: 1 1 12em; color: var(--slate); }
  .ov-customers.no-alerts:not(.show-all) .cust-quiet-line { padding-top: 0; border-top: 0; }
}

/* Vendors and trunk groups: each switch under its band, as on Rotation, in Rotation's compact
   table rows (css/rotation.css draws them, two short lines each in a narrow column). On a phone
   they fold behind one button. */
.ov-title-count { color: var(--slate); font-weight: 400; }
.ov-vendors-toggle { display: none; }
.ov-kind + .ov-kind { margin-top: 16px; }
.ov-vendors .rotation-switch + .rotation-switch { margin-top: 16px; }
.ov-vendors .rotation-band { min-height: 40px; padding: 6px 12px; }
.ov-vendors .rotation-band-name { font-size: 15px; }
.ov-vendors .rotation-band-meta { font-size: 13px; }
.ov-vendors .rotation-sub { margin-top: 8px; }
@media (max-width: 719px) {
  .ov-vendors-toggle { display: flex; width: 100%; }
  .ov-vendors:not(.open) .ov-vendors-body, .ov-vendors:not(.open) .panel-sub { display: none; }
  .ov-vendors:not(.open) .panel-head { margin-bottom: 12px; }
  .ov-vendors.open .ov-vendors-toggle { margin-bottom: 16px; }
}
@media (min-width: 1200px) and (pointer: fine) {
  .ov-vendors .rotation-t-actions .btn { min-height: 32px; padding: 0 10px; font-size: 13px; }
}

/* The calls chart. Out of date, its title says so in amber. */
.hero .panel-head { margin-bottom: 12px; }
.hero.stale .panel-head h2 { color: var(--warn-text); }
.hero-updated { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rule); color: var(--slate); font-size: 13px; }
@media (max-width: 719px) {
  .hero .panel-actions { width: 100%; }
  .hero .seg { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); width: 100%; }
  .hero .panel-sub { display: none; }
  .hero-body > .hero-updated { order: 3; }
  /* Fresh figures need no word on a phone; out of date, the title and Right now say so too. */
  .hero:not(.stale) .hero-updated { display: none; }
}

/* The rows of Rotation and the vendor screen (views/rotation.js): a vendor or trunk group with
   its number strip. */
.rot-list { margin: 0; padding: 0; list-style: none; }
.rot {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "main" "strip" "foot" "side";
  gap: 10px;
  padding: 16px 0;
  border-top: 1px solid var(--rule);
}
.rot-main { grid-area: main; min-width: 0; }
.rot-side { grid-area: side; min-width: 0; }
.rot-strip { grid-area: strip; min-width: 0; }
.rot-foot { grid-area: foot; min-width: 0; }
.rot-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; }
.rot-name { margin: 0; font-size: 17px; line-height: 1.3; }
.rot-name a { display: inline-flex; align-items: center; min-height: var(--tap); margin: -10px 0; color: var(--ink); text-decoration: none; }
.rot-name a:hover { color: var(--accent); text-decoration: underline; }
.rot-where { margin: 0; color: var(--slate); font-size: 14px; }
.rot-now { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 20px; margin: 0; font-size: 14px; }
.rot-batch { display: inline-flex; align-items: center; font-weight: 700; }
.rot-detail { margin: 4px 0 0; font-size: 14px; font-weight: 700; }
.rot-detail.bad { color: var(--bad); }
.rot-detail.warn { color: var(--warn-text); }
.rot-detail.off { color: var(--slate); font-weight: 400; }
.rot-detail.test { color: var(--test); font-weight: 400; }
.rot-actions { display: flex; gap: 8px; }
.rot-actions .btn { flex: 1 1 0; }
.rot-side .cmd-line { margin-top: 6px; }
@media (min-width: 720px) {
  .rot { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "main side" "strip strip" "foot foot"; gap: 10px 24px; }
  .rot-actions .btn { flex: none; }
  .rot-side .cmd-line { max-width: 300px; margin-left: auto; text-align: right; }
}

/* A screen that is not built yet. */
.placeholder-link { margin-top: 8px; }

/* A view without bare: true sits on one white panel (app.js). Every screen is bare now. */
.legacy-page { max-width: 1040px; padding: 20px 16px; border: 1px solid var(--rule); border-radius: 12px; background: var(--paper); }
@media (min-width: 720px) { .legacy-page { padding: 28px 32px; } }

/* ----------------------------------------------------------- toasts and dialogs */
.toasts {
  position: fixed;
  right: 0;
  bottom: calc(var(--tab-h) + 12px + env(safe-area-inset-bottom));
  left: 0;
  z-index: 60;
  display: grid;
  justify-items: center;
  gap: 8px;
  padding: 0 var(--gutter);
  pointer-events: none;
}
@media (min-width: 960px) { .toasts { right: 24px; bottom: 24px; left: auto; justify-items: end; padding: 0; } }
.toast {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  max-width: 440px;
  padding: 4px 4px 4px 14px;
  border-radius: 8px;
  background: var(--toast-bg);
  color: var(--toast-text);
  pointer-events: auto;
}
.toast::before {
  content: ""; flex: none; width: 15px; height: 15px; background: #7DC8FF;
  -webkit-mask: var(--lamp-info) center / contain no-repeat; mask: var(--lamp-info) center / contain no-repeat;
}
.toast-ok::before { background: #4FCB98; -webkit-mask-image: var(--lamp-ok); mask-image: var(--lamp-ok); }
.toast-warn::before { background: #F0A458; -webkit-mask-image: var(--lamp-warn); mask-image: var(--lamp-warn); }
.toast-err::before { background: #FF7A8A; -webkit-mask-image: var(--lamp-bad); mask-image: var(--lamp-bad); }
.toast-test::before { background: #AE92F5; -webkit-mask-image: var(--lamp-test); mask-image: var(--lamp-test); }
:root[data-theme="dark"] .toast::before { background: #0C5C97; }
:root[data-theme="dark"] .toast-ok::before { background: #11795A; }
:root[data-theme="dark"] .toast-warn::before { background: #B4580B; }
:root[data-theme="dark"] .toast-err::before { background: #C42B3C; }
:root[data-theme="dark"] .toast-test::before { background: #6B3FC4; }
.toast p { flex: 1 1 auto; min-width: 0; padding: 8px 0; font-size: 14px; overflow-wrap: anywhere; }
.toast button {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0 10px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: inherit;
  font-size: 14px;
  font-weight: 700;
}
.toast .toast-btn { border: 1px solid currentColor; }
.toast button:hover { background: rgba(127, 140, 160, 0.22); }
.toast button:focus-visible { outline-color: currentColor; }

dialog {
  width: min(480px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  overflow: auto;
  padding: 24px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--paper);
  color: var(--ink);
}
dialog::backdrop { background: var(--scrim); }
dialog h2 { margin: 0 0 8px; font-size: 18px; font-weight: 700; line-height: 1.3; }
.dlg-body { color: var(--ink); }
.dlg-body p + p, .dlg-body p + ul, .dlg-body ul + p { margin-top: 8px; }
.dlg-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 24px; }

/* -------------------------------------------------------------------- popover */
.popover {
  position: fixed;
  z-index: 70;
  width: min(320px, calc(100vw - 32px));
  padding: 14px 16px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--paper);
  box-shadow: var(--float-shadow);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.5;
}
.popover::before {
  content: "";
  position: absolute;
  top: -7px;
  left: var(--pop-x, 20px);
  width: 12px;
  height: 12px;
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  background: var(--paper);
  transform: rotate(45deg);
}
.popover.above::before { top: auto; bottom: -7px; transform: rotate(225deg); }
.popover-title { margin: 0 0 4px; font-size: 16px; font-weight: 700; }
.popover-more { margin-top: 8px; }
.popover-list-title { margin-top: 8px; font-weight: 700; }
.popover-list { margin: 2px 0 0; padding: 0; list-style: none; }
.popover-list li { padding: 2px 0; }
.popover a { font-weight: 700; }

/* ---------------------------------------------------------------------- tour */
/* The ring lights one element while the rest of the page dims. It sits above the sticky top bar
   and the tab bar, so a step can light Stop all rotation where it is (ui.js frames an element on
   the bars without scrolling, and keeps every other ring out of them). */
.tour-ring {
  position: fixed;
  z-index: 49;
  border-radius: 12px;
  box-shadow: 0 0 0 3px var(--accent), 0 0 0 9999px var(--tour-dim);
  pointer-events: none;
  transition: left 200ms ease, top 200ms ease, width 200ms ease, height 200ms ease;
}
.tour {
  position: fixed;
  z-index: 50;
  width: min(360px, calc(100vw - 32px));
  padding: 16px 16px 12px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--paper);
  box-shadow: var(--float-shadow);
  color: var(--ink);
}
.tour::before {
  content: "";
  position: absolute;
  left: var(--tour-x, 24px);
  width: 14px;
  height: 14px;
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  background: var(--paper);
}
.tour.below::before { top: -8px; transform: rotate(45deg); }
.tour.above::before { bottom: -8px; transform: rotate(225deg); }
.tour-step { margin: 0 0 4px; color: var(--accent); font-size: 13px; font-weight: 700; }
.tour h2 { margin: 0 0 6px; font-size: 18px; font-weight: 700; }
.tour p { margin: 0; }
.tour-actions { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
.tour-gap { flex: 1; }

/* -------------------------------------------------------------------- motion */
/* The transitions themselves (sections, windows, toasts, what a button opens, buttons) are in
   css/motion.css, which loads last and applies only without "reduce motion". */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
}
