/* ============================================================
   CircleISP — Application Stylesheet
   Utilitarian, blue-toned, no gradients, no emojis
   Dark / Light via [data-theme] on <html>
   ============================================================ */

@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@300;400;500;600&display=swap');

/* ── CSS Variables ─────────────────────────────────────────── */

:root,
[data-theme="light"] {
  /* Blues */
  --blue-950: #0a1628;
  --blue-900: #0f2040;
  --blue-800: #163460;
  --blue-700: #1d4a8a;
  --blue-600: #2563b0;
  --blue-500: #3b7dd8;
  --blue-400: #6ba3e8;
  --blue-300: #a0c4f4;
  --blue-200: #cce0fa;
  --blue-100: #e8f2fd;
  --blue-50:  #f4f9ff;

  /* Backgrounds */
  --bg-base:    #f8fafd;
  --bg-surface: #ffffff;
  --bg-raised:  #f0f6ff;
  --bg-sunken:  #e8f0fb;

  /* Borders */
  --border:       #d0ddf0;
  --border-focus: #2563b0;
  --border-strong: #9ab4d8;

  /* Text */
  --text-primary:   #0f1e38;
  --text-secondary: #3a5070;
  --text-muted:     #6a8aaa;
  --text-on-blue:   #ffffff;

  /* Accent / interactive */
  --accent:       #2563b0;
  --accent-hover: #163460;
  --accent-light: #e8f2fd;

  /* Status */
  --status-online:  #1a6b3a;
  --status-offline: #9b1c1c;
  --status-pending: #7c5200;
  --status-online-bg:  #d4f0e0;
  --status-offline-bg: #fce8e8;
  --status-pending-bg: #fff3cd;

  /* Money. A sixth hue rather than a reuse: the audit log needs "somebody paid"
     to be findable by colour on a page where green already means "access
     granted", and the two co-occur constantly. Violet is the only family not
     already spoken for by a status. */
  --status-money:    #6b21a8;
  --status-money-bg: #f3e8ff;

  /* Nav */
  --nav-bg:     #0f2040;
  --nav-text:   #c8d8f0;
  --nav-active: #ffffff;
  --nav-border: #163460;

  /* Chart series — fixed order, assigned by series identity and never cycled.
     Distinct from the status colours above, which are reserved for state and
     must never be reused as a series hue.
     Validated for colour-blind separation against the light surface (#ffffff):
     protan/deutan ΔE 22.8, normal ΔE 28.1, both well clear of the floor. Do not
     hand-tweak these — re-run the palette validator if they ever change. */
  --chart-1: #2563b0;  /* accent blue */
  --chart-2: #b45309;  /* amber */

  /* Shadows */
  --shadow-sm: 0 1px 3px rgba(15, 32, 64, 0.08);
  --shadow-md: 0 3px 8px rgba(15, 32, 64, 0.12);
}

[data-theme="dark"] {
  --blue-50:  #0a1628;
  --blue-100: #0f2040;
  --blue-200: #163460;

  --bg-base:    #070e1c;
  --bg-surface: #0d1b35;
  --bg-raised:  #122040;
  --bg-sunken:  #091428;

  --border:       #1d3560;
  --border-focus: #3b7dd8;
  --border-strong: #2a4a80;

  --text-primary:   #d8e8f8;
  --text-secondary: #8aaed4;
  --text-muted:     #506888;
  --text-on-blue:   #ffffff;

  --accent:       #3b7dd8;
  --accent-hover: #6ba3e8;
  --accent-light: #122040;

  --status-online:  #4ade80;
  --status-offline: #f87171;
  --status-pending: #fbbf24;
  --status-online-bg:  #0d2e1a;
  --status-offline-bg: #2e0d0d;
  --status-pending-bg: #2e1e00;

  /* Stepped for the dark surface rather than reused from light, like the chart
     series below: the light violet is far outside the dark lightness band. */
  --status-money:    #d8b4fe;
  --status-money-bg: #2a1040;

  --nav-bg:     #050c1a;
  --nav-text:   #6a8aaa;
  --nav-active: #d8e8f8;
  --nav-border: #0f2040;

  /* Chart series, stepped for the dark surface (#0d1b35) rather than reused
     from light — the light amber sits outside the dark lightness band and would
     glare. Validated on the dark surface: protan ΔE 26.5, normal ΔE 31.6. */
  --chart-1: #3b7dd8;
  --chart-2: #d97706;

  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 3px 8px rgba(0, 0, 0, 0.4);
}

/* ── Reset & Base ──────────────────────────────────────────── */

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* The browser hides [hidden] with a user-agent rule, and ANY author `display`
   beats a user-agent one whatever its specificity. So `.btn { display:
   inline-flex }` quietly un-hides every hidden button — which is how the
   wizard's Back and submit buttons were both on screen at step one while the
   panels either side of them hid correctly.

   Every Stimulus controller here toggles visibility with the `hidden` property
   (access-option, payment-channel, billing-payment, lazy-panel, wizard), so
   this has to hold for all of them, not just the one that noticed. */
[hidden] {
  display: none !important;
}

html {
  font-size: 15px;
  -webkit-font-smoothing: antialiased;
}

body {
  font-family: 'IBM Plex Sans', sans-serif;
  font-weight: 400;
  background: var(--bg-base);
  color: var(--text-primary);
  line-height: 1.6;
  min-height: 100vh;
}

/* Decorative wave, layered between the page background and the content.
   z-index -1 puts it above the canvas (body's background propagates there,
   since html sets none) but below every in-flow box, so the opaque cards and
   the sticky nav keep painting over it with no z-index of their own. */
body::before {
  content: "";
  position: fixed;
  inset: auto 0 0 0;
  aspect-ratio: 2000 / 774; /* the PNG's own ratio: the box cannot crop the art */
  background: url("/assets/magnific-21df02cd.png") no-repeat center bottom / contain;
  opacity: 0.55;
  pointer-events: none;
  z-index: -1;
}

/* The wave is a saturated blue that was drawn for a light page; at full
   strength it glares against the dark background instead of sitting under it. */
[data-theme="dark"] body::before {
  opacity: 0.3;
}

/* ── Navigation ────────────────────────────────────────────── */

.nav {
  background: var(--nav-bg);
  border-bottom: 1px solid var(--nav-border);
  position: sticky;
  top: 0;
  z-index: 100;
}

.nav-inner {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 1.5rem;
  display: flex;
  align-items: center;
  gap: 0;
  height: 52px;
}

.nav-brand {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--nav-active);
  text-decoration: none;
  letter-spacing: 0.04em;
  margin-right: 2rem;
  flex-shrink: 0;
}

.nav-brand span {
  color: var(--blue-400);
}

/* On wide screens the drawer is just a transparent wrapper around the bar's
   link list, so it has to pass its own flex sizing straight through. */
.nav-menu {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 0;
  flex: 1;
  list-style: none;
}

.nav-links a {
  display: block;
  padding: 0 0.875rem;
  height: 52px;
  line-height: 52px;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--nav-text);
  text-decoration: none;
  letter-spacing: 0.02em;
  border-bottom: 2px solid transparent;
  transition: color 0.15s, border-color 0.15s;
}

.nav-links a:hover,
.nav-links a.active {
  color: var(--nav-active);
  border-bottom-color: var(--blue-500);
}

/* ── Nav groups ──────────────────────────────────────────────────────────────
   PPPoE and Hotspot are separate products, so they group rather than sit flat.
   The same markup is a hover/click dropdown on the wide bar and an inline
   disclosure in the mobile drawer — one information architecture, two shapes.

   Groups nest (Users › PPPoE › Plans), so every selector below reaches its own
   list with `>`. A descendant selector here would make opening Users open every
   group inside it at once. */
.nav-group { position: relative; }

.nav-group-toggle {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0 0.875rem;
  height: 52px;
  font-size: 0.8125rem;
  font-weight: 500;
  font-family: inherit;
  color: var(--nav-text);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}

.nav-group-toggle:hover,
.nav-group-toggle.active {
  color: var(--nav-active);
  border-bottom-color: var(--blue-500);
}

.nav-group-caret {
  width: 0;
  height: 0;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  border-top: 4px solid currentColor;
  opacity: 0.7;
  transition: transform 0.15s;
}

.nav-sublinks {
  list-style: none;
  position: absolute;
  top: 52px;
  left: 0;
  min-width: 180px;
  background: var(--nav-bg);
  border: 1px solid var(--nav-border);
  border-radius: 0 0 8px 8px;
  padding: 0.25rem 0;
  z-index: 210;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 0.15s, transform 0.15s, visibility 0.15s;
}

/* Hover-only on the wide bar. `.is-expanded` is the mobile drawer's accordion
   state — it is server-rendered onto the current page's group and toggled by
   click, and nothing up here removes it, so letting it open the flyout pins the
   dropdown open permanently instead of drawing back up on mouse-leave. */
.nav-group:hover > .nav-sublinks {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.nav-sublinks a {
  height: auto;
  line-height: 1.4;
  padding: 0.625rem 1rem;
  border-bottom: none;
  border-left: 2px solid transparent;
  white-space: nowrap;
}

.nav-sublinks a:hover,
.nav-sublinks a.active {
  border-bottom-color: transparent;
  border-left-color: var(--blue-500);
  background: rgba(255, 255, 255, 0.04);
}

/* Second level. Its toggle has to read as a sibling of the links around it, and
   its list flies out to the side — there is no room below a dropdown item. */
.nav-sublinks .nav-group-toggle {
  width: 100%;
  height: auto;
  line-height: 1.4;
  padding: 0.625rem 1rem;
  justify-content: space-between;
  border-bottom: none;
  border-left: 2px solid transparent;
  white-space: nowrap;
}

.nav-sublinks .nav-group-toggle:hover,
.nav-sublinks .nav-group-toggle.active {
  border-bottom-color: transparent;
  border-left-color: var(--blue-500);
  background: rgba(255, 255, 255, 0.04);
}

/* Slides in from the side, so it overrides the parent's translateY pair. The
   extra class outweighs `.nav-group:hover > .nav-sublinks` above. */
.nav-sublinks .nav-sublinks {
  top: 0;
  left: 100%;
  border-radius: 0 8px 8px 0;
  transform: translateX(-4px);
}

.nav-sublinks .nav-group:hover > .nav-sublinks {
  transform: translateX(0);
}

/* A nested caret points at its flyout rather than down at nothing. */
.nav-sublinks .nav-group-caret { transform: rotate(-90deg); }

.nav-right {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-left: auto;
}

.nav-user {
  font-size: 0.75rem;
  color: var(--nav-text);
  letter-spacing: 0.02em;
}

.nav-user-operator {
  color: var(--blue-400);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
}

/* ── Off-canvas drawer ─────────────────────────────────────────
   Above the breakpoint the links are the horizontal bar and the drawer
   chrome (toggle, header, overlay) is absent; the media query at the end
   of this file turns the same markup into a sliding panel. */

.nav-toggle,
.nav-menu-header,
.nav-overlay {
  display: none;
}

.nav-toggle {
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  margin: 0 0.75rem 0 -0.5rem;
  padding: 0;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid var(--nav-border);
  color: var(--nav-text);
  cursor: pointer;
}

.nav-toggle:hover {
  border-color: var(--blue-500);
}

/* Three bars drawn from one element: the middle is the box itself, the
   outer two are its pseudo-elements. */
.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  display: block;
  width: 16px;
  height: 1.5px;
  background: currentColor;
}

.nav-toggle-bars::before,
.nav-toggle-bars::after {
  content: "";
  position: relative;
}

.nav-toggle-bars::before { top: -5px; }
.nav-toggle-bars::after  { top: 3.5px; }

.nav-close {
  background: transparent;
  border: none;
  color: var(--nav-text);
  font-size: 1.5rem;
  line-height: 1;
  padding: 0 0.25rem;
  cursor: pointer;
}

.nav-close:hover { color: var(--nav-active); }

.btn-theme {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.25rem 0.625rem;
  background: transparent;
  color: var(--nav-text);
  border: 1px solid var(--nav-border);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
  line-height: 1.4;
}

.btn-theme:hover {
  color: var(--nav-active);
  border-color: var(--blue-500);
}

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

.page {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem 1.5rem;
}

.page-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 1.75rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--border);
  gap: 1rem;
  flex-wrap: wrap;
}

.page-header h1 {
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 1.375rem;
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}

.page-header-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* ── Breadcrumb ────────────────────────────────────────────── */

.breadcrumb {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-bottom: 1.25rem;
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.breadcrumb a {
  color: var(--accent);
  text-decoration: none;
}

.breadcrumb a:hover { text-decoration: underline; }

.breadcrumb-sep {
  color: var(--border-strong);
}

/* ── Alerts / Flash ────────────────────────────────────────── */

.flash {
  padding: 0.625rem 1rem;
  font-size: 0.8125rem;
  border-left: 3px solid;
  margin-bottom: 1.25rem;
  background: var(--bg-surface);
}

.flash.notice {
  border-color: var(--blue-600);
  color: var(--text-secondary);
  background: var(--accent-light);
}

.flash.alert {
  border-color: var(--status-offline);
  color: var(--status-offline);
  background: var(--status-offline-bg);
}

/* ── Buttons ───────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.4375rem 0.875rem;
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 0.8125rem;
  font-weight: 500;
  cursor: pointer;
  text-decoration: none;
  border: 1px solid transparent;
  line-height: 1.4;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  white-space: nowrap;
}

.btn-primary {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

.btn-primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.btn-secondary {
  background: transparent;
  color: var(--text-secondary);
  border-color: var(--border);
}

.btn-secondary:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.btn-danger {
  background: transparent;
  color: var(--status-offline);
  border-color: var(--border);
}

.btn-danger:hover {
  background: var(--status-offline-bg);
  border-color: var(--status-offline);
}

.btn-sm {
  padding: 0.25rem 0.625rem;
  font-size: 0.75rem;
}

/* A disabled button is a state rather than an action: it says the step exists
   and is not available yet — a tenant with no signup to confirm. Muted and
   inert to hover, but still readable, because that is the point of showing it. */
.btn:disabled,
.btn[disabled],
.btn:disabled:hover,
.btn[disabled]:hover {
  background: transparent;
  color: var(--text-muted);
  border-color: var(--border);
  cursor: not-allowed;
}

/* ── Cards ─────────────────────────────────────────────────── */

.card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  margin-bottom: 1rem;
}

.card-header {
  padding: 0.875rem 1.25rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.card-header h2,
.card-header h3 {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: 0.01em;
}

.card-body {
  padding: 1.25rem;
}

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

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  background: var(--bg-surface);
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8125rem;
}

thead tr {
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
}

thead th {
  padding: 0.625rem 1rem;
  text-align: left;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}

tbody tr {
  border-bottom: 1px solid var(--border);
  transition: background 0.1s;
}

tbody tr:last-child {
  border-bottom: none;
}

tbody tr:hover {
  background: var(--bg-raised);
}

td {
  padding: 0.625rem 1rem;
  color: var(--text-primary);
  vertical-align: middle;
}

td.mono {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.775rem;
  color: var(--text-secondary);
}

td.actions {
  white-space: nowrap;
  text-align: right;
}

/* ── Collapsible table columns ─────────────────────────────────
   A phone cannot hold ten columns, and the horizontal scroll hides whichever
   ones you are not looking at without saying they are there. Columns marked
   .col-detail fold away below the breakpoint and reappear in a panel under
   their own row, opened by the caret in .col-toggle — the same values, one
   tap away, in the space of one column. Wide screens are untouched: there the
   columns show and the panel is display:none regardless of its open class, so
   a row left open by a resize costs nothing. */

th.col-toggle, td.col-toggle { display: none; }

.row-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--accent);
}

.row-toggle-caret {
  width: 0;
  height: 0;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  border-top: 4px solid currentColor;
  opacity: 0.7;
  transition: transform 0.15s;
}

.row-toggle[aria-expanded="true"] .row-toggle-caret { transform: rotate(180deg); }

.row-detail { display: none; }

.row-detail > td {
  padding: 0.25rem 1rem 0.875rem;
  background: var(--bg-raised);
}

/* Label above value, so a full MAC or a router name is never squeezed into
   half a phone's width. */
.row-detail-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.625rem 1.25rem;
}

.row-detail-list dt {
  margin-bottom: 0.15rem;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.row-detail-list dd { font-size: 0.8125rem; }

@media (max-width: 768px) {
  th.col-detail, td.col-detail { display: none; }
  th.col-toggle, td.col-toggle { display: table-cell; }
  tr.row-detail.is-open { display: table-row; }
}

/* ── Status Badges ─────────────────────────────────────────── */

.badge {
  display: inline-block;
  padding: 0.1875rem 0.5rem;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.badge-online {
  background: var(--status-online-bg);
  color: var(--status-online);
}

.badge-offline {
  background: var(--status-offline-bg);
  color: var(--status-offline);
}

.badge-pending, .badge-suspended {
  background: var(--status-pending-bg);
  color: var(--status-pending);
}

.badge-active {
  background: var(--status-online-bg);
  color: var(--status-online);
}

.badge-cancelled {
  background: var(--bg-sunken);
  color: var(--text-muted);
}

.badge-managed {
  background: var(--accent-light);
  color: var(--accent);
}

.badge-overlimit {
  background: var(--status-offline-bg);
  color: var(--status-offline);
}

/* ── Audit action badges ───────────────────────────────────────
   Coloured by what the verb did to somebody's service, not by which
   family it came from — see AuditEventsHelper::ACTION_TONES. */

.badge-audit-gain {
  background: var(--status-online-bg);
  color: var(--status-online);
}

.badge-audit-money {
  background: var(--status-money-bg);
  color: var(--status-money);
}

.badge-audit-change {
  background: var(--accent-light);
  color: var(--accent);
}

.badge-audit-warn {
  background: var(--status-pending-bg);
  color: var(--status-pending);
}

.badge-audit-loss {
  background: var(--status-offline-bg);
  color: var(--status-offline);
}

/* Machine-generated rows. Bordered rather than filled so a page of them reads
   as background texture — they are only shown when asked for, and must never
   out-shout the deliberate acts sitting among them. */
.badge-audit-quiet {
  background: transparent;
  color: var(--text-muted);
  box-shadow: inset 0 0 0 1px var(--border);
}

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

.form-group {
  margin-bottom: 1.125rem;
}

label {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.375rem;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
select,
textarea {
  width: 100%;
  padding: 0.5rem 0.75rem;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  color: var(--text-primary);
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 0.875rem;
  outline: none;
  transition: border-color 0.15s;
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--border-focus);
}

input[type="checkbox"] {
  accent-color: var(--accent);
  width: 1rem;
  height: 1rem;
  cursor: pointer;
}

textarea {
  resize: vertical;
  min-height: 6rem;
}

.form-hint {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 0.25rem;
}

.form-error {
  font-size: 0.75rem;
  color: var(--status-offline);
  margin-top: 0.25rem;
}

.field-errors {
  background: var(--status-offline-bg);
  border-left: 3px solid var(--status-offline);
  padding: 0.75rem 1rem;
  margin-bottom: 1.25rem;
}

.field-errors h2 {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--status-offline);
  margin-bottom: 0.5rem;
}

.field-errors ul {
  padding-left: 1.25rem;
  font-size: 0.8rem;
  color: var(--status-offline);
}

details summary {
  cursor: pointer;
  font-size: 0.8125rem;
  color: var(--accent);
  margin-bottom: 0.75rem;
  user-select: none;
}

/* ── Stat Blocks ───────────────────────────────────────────── */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  margin-bottom: 1.5rem;
}

.stat {
  background: var(--bg-surface);
  padding: 1.25rem 1.5rem;
}

.stat-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.375rem;
}

.stat-value {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 1.5rem;
  font-weight: 500;
  color: var(--text-primary);
  line-height: 1.2;
}

.stat-sub {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 0.25rem;
}

/* ── Definition lists ─────────────────────────────────────── */

.dl-grid {
  display: grid;
  grid-template-columns: 10rem 1fr;
  gap: 0;
  border: 1px solid var(--border);
  background: var(--bg-surface);
  font-size: 0.8125rem;
}

.dl-grid dt {
  padding: 0.625rem 1rem;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
  display: flex;
  align-items: center;
}

.dl-grid dd {
  padding: 0.625rem 1rem;
  border-bottom: 1px solid var(--border);
  color: var(--text-primary);
  display: flex;
  align-items: center;
}

.dl-grid dt:last-of-type,
.dl-grid dd:last-of-type {
  border-bottom: none;
}

/* ── Usage bar ─────────────────────────────────────────────── */

.usage-bar-wrap {
  height: 6px;
  background: var(--bg-sunken);
  width: 100%;
  max-width: 300px;
  overflow: hidden;
}

.usage-bar-fill {
  height: 100%;
  background: var(--accent);
  transition: width 0.3s;
}

.usage-bar-fill.warn { background: var(--status-pending); }
.usage-bar-fill.danger { background: var(--status-offline); }

/* ── Portal layout ─────────────────────────────────────────── */

.portal-layout {
  display: grid;
  grid-template-columns: 220px 1fr;
  min-height: calc(100vh - 52px);
}

.portal-sidebar {
  background: var(--bg-surface);
  border-right: 1px solid var(--border);
  padding: 1.5rem 0;
}

.portal-sidebar-label {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding: 0 1.25rem;
  margin-bottom: 0.5rem;
}

.portal-sidebar a {
  display: block;
  padding: 0.5rem 1.25rem;
  font-size: 0.8125rem;
  color: var(--text-secondary);
  text-decoration: none;
  border-left: 2px solid transparent;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}

.portal-sidebar a:hover,
.portal-sidebar a.active {
  color: var(--accent);
  border-left-color: var(--accent);
  background: var(--accent-light);
}

.portal-main {
  padding: 2rem;
}

/* ── Inline actions / link groups ──────────────────────────── */

.action-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 1.5rem;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* ── Two-col form layout ───────────────────────────────────── */

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

@media (max-width: 640px) {
  .form-grid { grid-template-columns: 1fr; }
}

/* ── Operator badge ────────────────────────────────────────── */

.operator-notice {
  font-size: 0.75rem;
  color: var(--text-muted);
  border: 1px solid var(--border);
  padding: 0.375rem 0.75rem;
  background: var(--bg-raised);
  display: inline-block;
  margin-bottom: 1.25rem;
  font-family: 'IBM Plex Mono', monospace;
  letter-spacing: 0.02em;
}

/* ── Dividers / section titles ─────────────────────────────── */

.section-title {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.875rem;
  margin-top: 1.75rem;
  padding-bottom: 0.375rem;
  border-bottom: 1px solid var(--border);
}

.section-title:first-child { margin-top: 0; }

/* ── Empty state ───────────────────────────────────────────── */

.empty-state {
  padding: 3rem 1.5rem;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.875rem;
  border: 1px dashed var(--border);
  background: var(--bg-surface);
}

.empty-state p { margin-bottom: 1rem; }

/* ── Code / mono ───────────────────────────────────────────── */

code {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.825em;
  background: var(--bg-sunken);
  color: var(--text-secondary);
  padding: 0.1em 0.35em;
}

pre {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.8rem;
  background: var(--bg-sunken);
  color: var(--text-secondary);
  padding: 1rem 1.25rem;
  overflow-x: auto;
  border: 1px solid var(--border);
  line-height: 1.7;
}

/* ── Responsive nav ────────────────────────────────────────── */

@media (max-width: 768px) {
  .nav-toggle { display: inline-flex; }
  .nav-brand  { margin-right: auto; }

  /* The bar's identity label goes to the drawer header, where there is room
     for it; keeping both would print the provider name twice. */
  .nav-right > .nav-user { display: none; }

  .nav-menu {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(80vw, 272px);
    z-index: 200;
    display: flex;
    flex-direction: column;
    /* the bar's centring would shrink-wrap the header and the list to their
       text and float them in the middle of the panel */
    align-items: stretch;
    background: var(--nav-bg);
    border-right: 1px solid var(--nav-border);
    overflow-y: auto;
    overscroll-behavior: contain;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform 0.22s ease, visibility 0.22s;
  }

  .nav-menu.is-open {
    transform: translateX(0);
    visibility: visible;
  }

  .nav-menu-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    height: 52px;
    flex-shrink: 0;
    padding: 0 0.75rem 0 1.25rem;
    border-bottom: 1px solid var(--nav-border);
  }

  .nav-menu-header .nav-user {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .nav-links {
    flex: 0 0 auto;
    flex-direction: column;
    align-items: stretch;
    padding: 0.5rem 0;
  }

  /* The bar's underline-on-active reads as a divider in a vertical list, so
     the marker moves to the leading edge. */
  .nav-links a {
    height: auto;
    line-height: 1.4;
    padding: 0.8rem 1.25rem;
    font-size: 0.875rem;
    border-bottom: none;
    border-left: 2px solid transparent;
  }

  .nav-links a:hover,
  .nav-links a.active {
    border-bottom-color: transparent;
    border-left-color: var(--blue-500);
    background: rgba(255, 255, 255, 0.04);
  }

  /* In the drawer the group opens in place — a floating dropdown inside an
     off-canvas panel has nowhere to float to. Hover must not open it either:
     on touch, hover sticks after a tap and the group never closes again. */
  .nav-group-toggle {
    width: 100%;
    height: auto;
    line-height: 1.4;
    padding: 0.8rem 1.25rem;
    font-size: 0.875rem;
    justify-content: space-between;
    border-bottom: none;
    border-left: 2px solid transparent;
  }

  .nav-group-toggle:hover,
  .nav-group-toggle.active {
    border-bottom-color: transparent;
    border-left-color: var(--blue-500);
    background: rgba(255, 255, 255, 0.04);
  }

  .nav-sublinks {
    position: static;
    min-width: 0;
    border: none;
    border-radius: 0;
    padding: 0;
    transform: none;
    display: none;
    background: rgba(0, 0, 0, 0.12);
  }

  .nav-group:hover > .nav-sublinks { display: none; }

  .nav-group.is-expanded > .nav-sublinks {
    display: block;
    opacity: 1;
    visibility: visible;
  }

  /* Every level opens downward in the drawer, so the sideways flyout geometry
     and its rotated caret are both undone here. */
  .nav-sublinks .nav-sublinks { left: auto; transform: none; }
  .nav-sublinks .nav-group-caret { transform: none; }

  .nav-group.is-expanded > .nav-group-toggle .nav-group-caret {
    transform: rotate(180deg);
  }

  /* Depth is carried by the indent alone — a third nested background tint on an
     already dark drawer stops being legible. */
  .nav-sublinks a,
  .nav-sublinks .nav-group-toggle { padding-left: 2.25rem; }

  .nav-sublinks .nav-group-toggle { font-size: 0.875rem; }

  .nav-sublinks .nav-sublinks { background: none; }
  .nav-sublinks .nav-sublinks a { padding-left: 3.25rem; }

  .nav-overlay {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 150;
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.22s ease, visibility 0.22s;
  }

  .nav-overlay.is-open {
    opacity: 1;
    visibility: visible;
  }

  body.nav-open { overflow: hidden; }

  .portal-layout {
    grid-template-columns: 1fr;
  }
  .portal-sidebar {
    border-right: none;
    border-bottom: 1px solid var(--border);
    padding: 0.75rem 0;
  }
  .portal-sidebar a { display: inline-block; padding: 0.375rem 1rem; }
}

@media (prefers-reduced-motion: reduce) {
  .nav-menu,
  .nav-overlay {
    transition: none;
  }
}

/* ── Turbo progress bar ────────────────────────────────────── */

.turbo-progress-bar {
  height: 2px;
  background: var(--blue-500);
}

/* ── Page transitions ──────────────────────────────────────── */

@media (prefers-reduced-motion: no-preference) {
  .page {
    animation: fadeUp 0.18s ease both;
  }
}

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Router billing ────────────────────────────────────────── */

.text-muted { color: var(--text-muted); }
.mono       { font-family: 'IBM Plex Mono', monospace; }

.billing-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1rem;
}

.progress-track {
  height: 6px;
  background: var(--bg-sunken);
  overflow: hidden;
  margin: 0.5rem 0;
}

.progress-fill    { height: 100%; }
.progress-ok      { background: var(--status-online); }
.progress-low     { background: var(--status-offline); }

.billing-dialog {
  border: 1px solid var(--border);
  background: var(--bg-raised, var(--bg, #fff));
  color: inherit;
  padding: 0;
  min-width: min(420px, 90vw);
  max-width: min(560px, 92vw);
  max-height: 88vh;
  overflow-y: auto;
  /* A modal <dialog> is centred by the user agent's own `margin: auto`, which
     the `*` reset above wipes out along with everyone else's margins — so every
     dialog in the app opened tucked into the top-left corner. Put it back here
     rather than exempting dialog from the reset, which would also hand back
     margins nobody asked for. */
  margin: auto;
}

.billing-dialog::backdrop {
  background: rgba(0, 0, 0, 0.45);
}

.billing-dialog-body {
  padding: 1rem 1.25rem 1.25rem;
}

/* billing_state badges (ProviderBillingAccount#billing_state) */
.badge-current {
  background: var(--status-online-bg);
  color: var(--status-online);
}

.badge-grace {
  background: var(--status-pending-bg);
  color: var(--status-pending);
}

/* A provider with no billing account at all — not a state, an absence. */
.badge-none {
  background: var(--bg-sunken);
  color: var(--text-muted);
}

/* ── Subscriber billing ────────────────────────────────────────
   Figures the platform bills on. Counts and money are read down a
   column and compared across rows, so they are right-aligned and
   set in the mono face like every other number in the app. */

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

td.num {
  font-family: 'IBM Plex Mono', monospace;
}

/* The count a provider is actually billed on, next to the one they
   are not. The weight is the only thing separating them. */
td.num-strong {
  color: var(--text-primary);
  font-weight: 600;
}

/* Closes a breakdown: the line above the sum. */
tbody tr.total-row td {
  border-top: 1px solid var(--border);
  background: var(--bg-raised);
}

/* Marks which of active/total this account is billed on. */
.stat-highlight {
  box-shadow: inset 3px 0 0 var(--accent);
}

/* Accounts past their covered date. Worth an operator's attention, so
   it reads as a status rather than as another number. */
.stat-alert .stat-value {
  color: var(--status-offline);
}

/* ── Tier editor ───────────────────────────────────────────────
   The price bands, and the live preview of what they would charge. */

.form-actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
  margin: 0.75rem 0;
}

.preview-card {
  margin-top: 1rem;
  background: var(--bg-sunken);
}

.preview-card .card-header input[type="number"] {
  width: 6.5rem;
  margin-left: 0.375rem;
}

/* Both modes side by side — the difference between them is the decision
   being made on this page, and it is invisible until you see the two
   numbers for the same count. */
.preview-mode {
  margin-bottom: 1rem;
}

.preview-mode h4 {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.375rem;
}

.preview-mode h4 strong {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.875rem;
  color: var(--text-primary);
  text-transform: none;
  letter-spacing: 0;
}

/* ── Onboarding one-liner ──────────────────────────────────── */

.code-box {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.75rem;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  padding: 0.75rem;
  margin: 0.5rem 0;
  white-space: pre-wrap;
  word-break: break-all;
  user-select: all;
}

/* Technician walkthrough inside the onboarding dialog */
.onboarding-guide {
  margin-top: 1rem;
  border-top: 1px solid var(--border);
  padding-top: 0.75rem;
}

.onboarding-guide h4 {
  font-size: 0.8rem;
  margin: 0.875rem 0 0.375rem;
}

.onboarding-guide p {
  font-size: 0.8125rem;
  margin: 0.5rem 0 0.25rem;
}

.onboarding-guide ol {
  padding-left: 1.25rem;
  font-size: 0.8125rem;
  margin: 0.25rem 0 0.5rem;
}

.onboarding-guide li {
  margin-bottom: 0.375rem;
}

/* ── Auto-refresh countdown ring ───────────────────────────── */

.refresh-ring {
  display: inline-flex;
  align-items: center;
}

.refresh-ring svg { display: block; }

.refresh-ring circle[data-auto-refresh-target="ring"] {
  transition: stroke-dashoffset 1s linear;
}

/* ── Provider dashboard ──────────────────────────────────────────────────────
   The landing screen reuses .stat/.stat-grid rather than introducing a second
   tile language for one page. It needs only two things on top: stats that are
   also destinations, and a way to say a figure is not known yet. */

a.stat {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: background 0.15s;
}

a.stat:hover { background: var(--bg-sunken); }

/* Not a zero — nothing has reported. Set at the size of the value it stands in
   for so the grid does not shift when a router starts reporting. */
.stat-idle {
  font-family: 'IBM Plex Sans', sans-serif;
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--text-muted);
  line-height: 2.05;
}

.dash-alert {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding: 0.875rem 1.25rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--status-offline);
  background: var(--bg-surface);
  font-size: 0.8125rem;
  margin-bottom: 1.5rem;
}

.dash-alert strong { color: var(--status-offline); }
.dash-alert span { color: var(--text-secondary); flex: 1; min-width: 0; }

/* ── Income ──────────────────────────────────────────────────────────────────
   The finance pages add no tile language of their own — they are .stat,
   .table-wrap and .progress-track like everything else. These are the four
   things those parts could not already do. */

/* The period bar puts two dates in a row, so they must not take the full width
   the form styles give every other input. */
.period-form {
  display: flex;
  gap: 0.375rem;
  flex-wrap: wrap;
  align-items: center;
}

.period-form input[type="date"] {
  width: auto;
  padding: 0.35rem 0.5rem;
  font-size: 0.8125rem;
}

/* One bar per month, two segments: what the provider kept, and what the
   platform took. Together they are gross, so bar width compares months. */
.progress-track.is-split { display: flex; }

/* Figures belong in a column that can be added up by eye: same glyph width,
   right-aligned, never wrapped mid-number. */
.amount-cell {
  text-align: right;
  font-family: 'IBM Plex Mono', monospace;
  white-space: nowrap;
}

/* Money leaving. The sign does the work; the colour only confirms it. */
.amount-out { color: var(--status-offline); }
