/* admin-v2.css — replacement stylesheet for /admin/* pages.

   Source: docs/design-v2/tokens.css (the prototype's design system).
   See docs/design-v2/HANDOFF.md for the design intent and porting notes.

   STATUS: parked. Not wired into any Razor page yet. The first ported
   page references this via @Assets["css/admin-v2.css"] and an opt-in
   layout (admin-v2), separate from the existing admin layout.
   admin.css continues to back every other admin page until the
   page-by-page port lands.

   ---

   ffb-bridge admin — design tokens.
   Mirrors the app's discipline (surface scale, type scale, pill system,
   mono numerals, signal=live-only) but the brand fork is INDIGO, not cobalt,
   to mark this as the admin surface — not the app, not the public site. */

:root,
[data-admin-theme="dark"] {
  --font-sans:    system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  --font-mono:    ui-monospace, "SFMono-Regular", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;

  /* Surfaces — dark */
  --bg:           #0E1013;
  --bg-2:         #13161A;
  --rail:         #0A0C0F;
  --rail-deep:    #08090B;
  --card:         #15181D;
  --card-2:       #191D23;
  --card-hi:      #1E232A;
  --well:         #0A0C0F;
  --well-deep:    #0D0F13;
  --overlay:      rgba(8, 9, 11, 0.72);

  /* Borders */
  --border:       #24292F;
  --border-hi:    #2F353D;
  --hairline:     #1C2027;

  /* Text */
  --fg:           #E7EAEE;
  --fg-dim:       #AEB4BD;
  --fg-mute:      #7A8089;
  --fg-faint:     #565B63;
  --fg-on-brand:  #0B1024;

  /* Brand — INDIGO. Admin's fork from the app's cobalt. */
  --brand:        #7C8CFF;
  --brand-hover:  #93A1FF;
  --brand-press:  #6577F0;
  --brand-soft:   rgba(124, 140, 255, 0.10);
  --brand-dim:    rgba(124, 140, 255, 0.18);
  --brand-ring:   rgba(124, 140, 255, 0.42);

  /* Signal amber — live / armed / "happening now" only */
  --signal:       #E8B53C;
  --signal-soft:  rgba(232, 181, 60, 0.12);
  --signal-ring:  rgba(232, 181, 60, 0.30);

  /* Status */
  --ok:           #4BB377;
  --warn:         #E8903C;
  --fail:         #E05656;
  --ok-soft:      rgba(75, 179, 119, 0.10);
  --warn-soft:    rgba(232, 144, 60, 0.10);
  --fail-soft:    rgba(224, 86, 86, 0.10);
  --ok-bd:        rgba(75, 179, 119, 0.30);
  --warn-bd:      rgba(232, 144, 60, 0.30);
  --fail-bd:      rgba(224, 86, 86, 0.30);

  /* "New" pill — uses signal language, but slightly muted */
  --new-bg:       rgba(232, 181, 60, 0.14);
  --new-fg:       #F0C44C;
  --new-bd:       rgba(232, 181, 60, 0.32);

  /* Shadows (very subtle in dark) */
  --shadow-1:     0 1px 0 0 rgba(255, 255, 255, 0.02) inset, 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-pop:   0 12px 32px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.4);
}

[data-admin-theme="light"] {
  --bg:           #EEF0F4;
  --bg-2:         #E4E7EC;
  --rail:         #FFFFFF;
  --rail-deep:    #F8F9FB;
  --card:         #FFFFFF;
  --card-2:       #F1F3F7;
  --card-hi:      #E8EBF0;
  --well:         #F4F5F7;
  --well-deep:    #ECEEF2;
  --overlay:      rgba(20, 22, 28, 0.42);

  --border:       #E2E5EA;
  --border-hi:    #C9CED6;
  --hairline:     #ECEEF2;

  --fg:           #15181D;
  --fg-dim:       #3F454E;
  --fg-mute:      #6B7280;
  --fg-faint:     #94A0AE;
  --fg-on-brand:  #FFFFFF;

  --brand:        #5563E0;
  --brand-hover:  #4453D8;
  --brand-press:  #3B49C7;
  --brand-soft:   rgba(85, 99, 224, 0.08);
  --brand-dim:    rgba(85, 99, 224, 0.14);
  --brand-ring:   rgba(85, 99, 224, 0.35);

  --signal:       #B57E0E;
  --signal-soft:  rgba(232, 181, 60, 0.18);
  --signal-ring:  rgba(232, 181, 60, 0.40);

  --ok:           #1F8A4E;
  --warn:         #B5630A;
  --fail:         #C13030;
  --ok-soft:      rgba(31, 138, 78, 0.08);
  --warn-soft:    rgba(181, 99, 10, 0.08);
  --fail-soft:    rgba(193, 48, 48, 0.08);
  --ok-bd:        rgba(31, 138, 78, 0.30);
  --warn-bd:      rgba(181, 99, 10, 0.30);
  --fail-bd:      rgba(193, 48, 48, 0.30);

  --new-bg:       rgba(181, 99, 10, 0.10);
  --new-fg:       #8C5208;
  --new-bd:       rgba(181, 99, 10, 0.30);

  --shadow-1:     0 1px 0 0 rgba(255, 255, 255, 0.6) inset, 0 1px 2px rgba(20, 22, 28, 0.06);
  --shadow-pop:   0 12px 32px rgba(20, 22, 28, 0.12), 0 2px 6px rgba(20, 22, 28, 0.06);
}

/* Density */
[data-density="compact"] {
  --row-h:      32px;
  --card-pad:   14px;
  --gap:        10px;
  --gap-lg:     14px;
}
[data-density="comfortable"], :root {
  --row-h:      40px;
  --card-pad:   20px;
  --gap:        14px;
  --gap-lg:     20px;
}

/* ============ base ============ */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection { background: var(--brand-dim); color: var(--fg); }

a {
  color: var(--brand);
  text-decoration: none;
}
a:hover { color: var(--brand-hover); text-decoration: underline; text-underline-offset: 3px; }

/* Card-as-link surfaces: hover should highlight the box, not
   underline the contents. Without this, the global a:hover
   underline cascades into pills, stat-deltas, mono numerals, and
   anything else inside .stat / .feed-row / .hero-strip / .chip,
   which reads as "this is a button" — distracting and inconsistent
   with the cleaner card affordance the design uses elsewhere. */
.stat:hover, .stat:hover *,
.feed-row:hover, .feed-row:hover *,
.hero-strip:hover, .hero-strip:hover *,
.chip:hover, .chip:hover * { text-decoration: none; }

.num, code, pre, kbd, .mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

button { font-family: inherit; }

/* ============ shell ============ */

.app {
  display: grid;
  grid-template-columns: 224px 1fr;
  grid-template-rows: 56px 1fr;
  grid-template-areas: "rail topbar" "rail main";
  min-height: 100vh;
  /* No horizontal page scroll on the admin. Tables that need to scroll
     do it inside their .tbl-wrap (overflow-x: auto on that container).
     A page-level horizontal scrollbar means something — usually a
     mis-positioned absolute child, like a sticky-note pin placed at a
     desktop xPercent rendering past the right edge on phone — has
     escaped its container, and clipping it is the right answer.
     Lives on .app rather than html/body so the rail/topbar's
     position: sticky still resolves against the viewport — putting
     it on the html element promotes <html> to a scroll container,
     which captures sticky descendants and breaks "menu follows page
     scroll". */
  overflow-x: clip;
}
[data-density="compact"] .app { grid-template-columns: 200px 1fr; }

/* ---- topbar ---- */

.topbar {
  grid-area: topbar;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 24px;
  background: var(--rail);
  border-bottom: 1px solid var(--hairline);
  /* Mirrors the rail: fixed-positioned so pinning is unconditional.
     Spans col 2 (right of the 224px rail). The mobile media query
     overrides `left` to 0 because the rail goes off-canvas there. */
  position: fixed;
  top: 0;
  left: 224px;
  right: 0;
  height: 56px;
  z-index: 30;
}
[data-density="compact"] .topbar { left: 200px; }
.topbar-search {
  flex: 1;
  max-width: 520px;
  margin: 0 auto;
  position: relative;
}
.topbar-search input {
  width: 100%;
  height: 32px;
  padding: 0 12px 0 36px;
  background: var(--card-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg);
  font-size: 12.5px;
  font-family: inherit;
}
.topbar-search input::placeholder { color: var(--fg-faint); }
.topbar-search input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-ring);
}
.icon-search {
  /* Positioned absolutely in any position:relative parent that wraps a
     search input. Used by the topbar search and the subs-filter search.
     The parent provides the box; .icon-search sits 10px from its left
     edge, vertically centred. Pair with `padding-left: 32px` on the
     <input> so its text doesn't overlap the icon. */
  position: absolute;
  left: 10px; top: 50%;
  transform: translateY(-50%);
  color: var(--fg-faint);
  width: 14px; height: 14px;
  pointer-events: none;
}
.topbar-search .kbd-hint {
  position: absolute;
  right: 8px; top: 50%;
  transform: translateY(-50%);
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  position: relative; /* anchor for .notif-panel */
}

/* ---- notifications bell + dropdown ---- */

.notif-btn {
  position: relative;
  overflow: visible; /* let the badge poke out of the button corner */
}
.notif-btn .admin-notification-count {
  position: absolute;
  top: -5px;
  right: -5px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 700;
  line-height: 1;
  padding: 0 4px;
  height: 15px;
  min-width: 15px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--signal); /* solid amber for both themes — readable on any topbar bg */
  color: #1A1208;            /* near-black warm tone, high contrast on amber */
  border: 2px solid var(--rail); /* "punches out" of the topbar so the pill stays distinct */
  pointer-events: none;
  box-sizing: content-box;
  /* Hidden by default — JS adds .has-activity to .notif-btn when count > 0,
     and the renderer fills .textContent. */
}
.notif-btn:not(.has-activity) .admin-notification-count {
  display: none;
}

.notif-panel {
  position: absolute;
  top: calc(100% - 4px);
  right: 0;
  width: 360px;
  max-height: 480px;
  display: none;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
  z-index: 50;
  overflow: hidden;
}
.notif-panel.open { display: flex; }

.notif-panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 10px 14px;
  font-size: 12px;
  font-weight: 600;
  color: var(--fg);
  border-bottom: 1px solid var(--hairline);
  background: var(--card-2);
}
.notif-hint {
  font-size: 10.5px;
  font-weight: 400;
  color: var(--fg-mute);
  letter-spacing: 0.01em;
}

.notif-list {
  overflow-y: auto;
  flex: 1;
}
.notif-empty {
  padding: 24px 16px;
  text-align: center;
  font-size: 12px;
  color: var(--fg-mute);
}

.notif-item {
  display: grid;
  grid-template-columns: 8px 1fr auto;
  gap: 10px;
  align-items: start;
  padding: 10px 14px;
  border-bottom: 1px solid var(--hairline);
  text-decoration: none;
  color: inherit;
  transition: background 0.08s ease;
}
.notif-item:last-child { border-bottom: none; }
.notif-item:hover { background: var(--card-hi); }

.notif-dot {
  width: 6px;
  height: 6px;
  margin-top: 6px;
  border-radius: 999px;
  background: transparent;
}
.notif-item.unread .notif-dot {
  background: var(--new-fg);
  box-shadow: 0 0 0 2px var(--new-bg);
}

.notif-body { min-width: 0; }
.notif-title {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 2px;
}
.notif-detail {
  font-size: 11.5px;
  color: var(--fg-mute);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.notif-time {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--fg-mute);
  white-space: nowrap;
  margin-top: 2px;
}

@media (max-width: 600px) {
  .notif-panel {
    width: calc(100vw - 24px);
    right: 12px;
    max-height: 70vh;
  }
  /* Page-head stacks vertically on narrow viewports. Side-by-side
     row layout (the desktop default) doesn't survive a heavy
     .head-actions on a phone width: the actions (date + IANA tz id,
     time-range chip rows, dropdown + two buttons) take their full
     content width because of flex-shrink:0, and .head-text gets
     squeezed down to a sliver — combined with min-width:0 the
     description wraps to one word per line. Stacking gives both
     rows the full viewport width. */
  .page-head {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }
  .page-head h1 { line-height: 1.25; }
  .page-head .sub { font-size: 12px; }
  .page-head .head-actions {
    flex-wrap: wrap;
    gap: 6px;
  }
  .page-head .head-actions .label {
    font-size: 10.5px;
    word-break: break-word;
  }
}

.topbar-version {
  font-size: 11px;
  font-weight: 500;
  color: var(--fg-mute);
  padding: 4px 8px;
  border-radius: 4px;
  border: 1px solid var(--hairline);
  background: var(--card-2);
  letter-spacing: 0.01em;
  cursor: help;
  user-select: none;
  white-space: nowrap;
}
[data-density="compact"] .topbar-version { font-size: 10.5px; padding: 3px 7px; }

/* ---- rail ---- */

.rail {
  grid-area: rail;
  background: var(--rail);
  border-right: 1px solid var(--hairline);
  padding: 14px 12px 24px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Pinned via position: fixed rather than position: sticky.
     Sticky was unreliable: any ancestor with overflow / transform /
     contain / filter steals the sticky's scroll-port and silently
     turns it into normal flow. The rail is 224px wide and visually
     covers grid column 1 — fixed positioning gets identical layout
     while making "menu pinned" a hard guarantee regardless of what
     a future page might add to the cascade. The grid still allocates
     column 1 because grid-template-columns is unaffected by an item
     leaving normal flow; .main lands in column 2 at the same x. */
  position: fixed;
  top: 0; left: 0;
  width: 224px;
  height: 100vh;
  overflow-y: auto;
  z-index: 20;
}
[data-density="compact"] .rail { width: 200px; }
.rail-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px 14px;
  border-bottom: 1px solid var(--hairline);
  margin-bottom: 10px;
}
.rail-brand .logo { width: 22px; height: 22px; }
.rail-brand .name {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fg);
}
.rail-brand .tag {
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--brand);
  background: var(--brand-soft);
  border: 1px solid var(--brand-dim);
  padding: 2px 5px;
  border-radius: 4px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-left: auto;
}

.rail-section {
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--fg-faint);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  padding: 14px 10px 6px;
  font-weight: 500;
}
[data-nav="flat"] .rail-section { display: none; }

.rail a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 6px;
  color: var(--fg-dim);
  font-size: 13px;
  font-weight: 500;
  position: relative;
  text-decoration: none;
}
.rail a:hover { background: var(--card); color: var(--fg); text-decoration: none; }
.rail a.active {
  background: var(--brand-soft);
  color: var(--brand);
}
.rail a.active::before {
  content: "";
  position: absolute;
  left: -12px;
  top: 8px; bottom: 8px;
  width: 3px;
  background: var(--brand);
  border-radius: 0 3px 3px 0;
}
.rail a .icon { width: 16px; height: 16px; flex: 0 0 16px; }
[data-icons="off"] .rail a .icon { display: none; }
[data-icons="off"] .rail a { gap: 0; }

.rail a .badge {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 10px;
  background: var(--card-hi);
  color: var(--fg-dim);
  padding: 1px 6px;
  border-radius: 999px;
  font-weight: 500;
  border: 1px solid var(--border);
}
.rail a .badge.new {
  background: var(--new-bg);
  color: var(--new-fg);
  border-color: var(--new-bd);
}
.rail a .badge.warn {
  background: var(--warn-soft);
  color: var(--warn);
  border-color: var(--warn-bd);
}
/* Sticky-note count — operational signal, NOT an alert. Barely
   tinted so it fades into the rail at a glance; .badge.new and
   .badge.warn are the loud channels for things that need
   attention. The faint yellow tint just identifies what the
   count is about without competing with real alerts. */
.rail a .badge.sticky-count {
  background: rgba(232, 181, 60, 0.07);
  color: var(--fg-mute);
  border-color: rgba(232, 181, 60, 0.18);
  border-radius: 2px;
  padding: 1px 5px;
}
[data-admin-theme="light"] .rail a .badge.sticky-count {
  background: rgba(181, 126, 14, 0.07);
  color: var(--fg-mute);
  border-color: rgba(181, 126, 14, 0.22);
}
[data-nav="grouped-no-badge"] .rail a .badge { display: none; }

.rail-foot {
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* Logout button matches the visual weight of a rail link
   (same padding, color, hover) but lives inside a <form> so
   sign-out can't be triggered by a stray GET. */
.rail-logout-form { margin: 0; }
.rail-logout {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 6px;
  background: transparent;
  border: none;
  color: var(--fg-dim);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
}
.rail-logout:hover { background: var(--card); color: var(--fg); }
.rail-logout .icon { width: 16px; height: 16px; flex: 0 0 16px; }
[data-icons="off"] .rail-logout .icon { display: none; }
[data-icons="off"] .rail-logout { gap: 0; }

.rail-version {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  padding: 8px 10px 2px;
  font-size: 10.5px;
  color: var(--fg-mute);
  cursor: help;
  user-select: none;
}
.rail-version .rv-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--ok) 20%, transparent);
  flex: 0 0 6px;
}
.rail-version .rv-semver {
  font-size: 11px;
  font-weight: 600;
  color: var(--fg-dim);
  letter-spacing: 0;
}
.rail-version .rv-meta {
  width: 100%;
  font-size: 10px;
  color: var(--fg-faint);
  padding-left: 12px;
  letter-spacing: 0;
}

/* ---- main ---- */

.main {
  grid-area: main;
  padding: 32px 32px 80px;
  /* Scales in steps so the dashboard's 4-up grid and side-by-side rows
     use available space on 1920+/4K monitors without forcing text-
     heavy pages (audit, profile) to flow at uncomfortable line lengths.
     Targets the device set the operator actually uses: iPad Pro 11/13,
     14" MBP (1512), 1920×1200, 2560 wide. */
  max-width: 1400px;
  width: 100%;
  /* CSS Grid tracks default to min-width: auto — a child wider than
     the track (long stack trace, wide mono token, table that doesn't
     wrap) would otherwise push the layout past the viewport. */
  min-width: 0;
}
@media (min-width: 1700px) { .main { max-width: 1600px; } }
@media (min-width: 2100px) { .main { max-width: 1800px; } }
@media (min-width: 2400px) { .main { max-width: 2000px; } }
[data-density="compact"] .main { padding: 20px 24px 60px; }

.page-head {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 24px;
}
.page-head .head-text { flex: 1; min-width: 0; }
.page-head h1 {
  /* Scales smoothly with viewport instead of stepping at fixed
     breakpoints. At a narrow phone width "Good afternoon, AdityaNag."
     wraps to two lines; clamp lets the heading shrink down to 16px
     so it fits, then ramps back to 22px on tablet+ widths. The
     1700/2100/2400 viewport rules at the bottom of this file
     override the ceiling for 4K. */
  font-size: clamp(16px, 4vw, 22px);
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0 0 4px;
  color: var(--fg);
  text-wrap: balance;
}
.page-head .sub {
  color: var(--fg-mute);
  font-size: 13px;
  margin: 0;
}
.page-head .crumb {
  font-size: 12px;
  color: var(--fg-mute);
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
}
.page-head .crumb a { color: var(--fg-mute); }
.page-head .crumb a:hover { color: var(--fg); }
.page-head .head-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* ---- buttons ---- */

.btn {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  gap: 6px;
  padding: 7px 12px;
  min-height: 32px;
  box-sizing: border-box;
  border-radius: 6px;
  font-size: 12.5px;
  font-weight: 500;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--fg);
  cursor: pointer;
  text-decoration: none;
  transition: background 100ms, border-color 100ms;
}
.btn:hover { background: var(--card-hi); border-color: var(--border-hi); text-decoration: none; }
.btn .icon { width: 14px; height: 14px; }
.btn.primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--fg-on-brand);
}
.btn.primary:hover { background: var(--brand-hover); border-color: var(--brand-hover); color: var(--fg-on-brand); }
.btn.danger {
  background: var(--fail-soft);
  border-color: var(--fail-bd);
  color: var(--fail);
}
.btn.danger:hover { background: var(--fail); border-color: var(--fail); color: #fff; }
.btn.ghost { background: var(--card-2); border-color: var(--hairline); color: var(--fg-dim); }
.btn.ghost:hover { background: var(--card-hi); border-color: var(--border); color: var(--fg); }
.btn.sm { padding: 4px 8px; font-size: 11.5px; gap: 4px; }
.btn.sm .icon { width: 12px; height: 12px; }
.btn.icon-only { padding: 7px; }

/* Sticky-notes placement toggle: green when armed so the operator
   can see at a glance that the next click will drop a pin. The
   crosshair cursor alone isn't a strong enough signal — feedback
   from the live notes inbox flagged this. Pulsing ring matches the
   notif-bell attention pattern. */
[data-sticky-notes-toggle][aria-pressed="true"] {
  background: var(--ok-soft);
  border-color: var(--ok-bd);
  color: var(--ok);
  box-shadow: 0 0 0 0 color-mix(in oklab, var(--ok) 40%, transparent);
  animation: sticky-toggle-pulse 1.6s ease-out infinite;
}
[data-sticky-notes-toggle][aria-pressed="true"]:hover {
  background: var(--ok);
  border-color: var(--ok);
  color: #fff;
}
@keyframes sticky-toggle-pulse {
  0%   { box-shadow: 0 0 0 0   color-mix(in oklab, var(--ok) 50%, transparent); }
  70%  { box-shadow: 0 0 0 6px color-mix(in oklab, var(--ok) 0%,  transparent); }
  100% { box-shadow: 0 0 0 0   color-mix(in oklab, var(--ok) 0%,  transparent); }
}

.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--brand-ring);
}

/* ---- pills + dots ---- */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid transparent;
  white-space: nowrap;
  font-weight: 500;
}
.pill.ok   { background: var(--ok-soft);   color: var(--ok);   border-color: var(--ok-bd); }
.pill.warn { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-bd); }
.pill.fail { background: var(--fail-soft); color: var(--fail); border-color: var(--fail-bd); }
.pill.new  { background: var(--new-bg);    color: var(--new-fg); border-color: var(--new-bd); }
.pill.idle { background: var(--card-2);    color: var(--fg-mute); border-color: var(--border); }
.pill.brand{ background: var(--brand-soft);color: var(--brand); border-color: var(--brand-dim); }

.dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 999px;
  background: currentColor;
}

.kbd {
  display: inline-flex;
  align-items: center;
  height: 18px;
  padding: 0 5px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  background: var(--card-2);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: 4px;
  color: var(--fg-mute);
  font-weight: 500;
}

/* ---- card ---- */

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: var(--card-pad);
  box-shadow: var(--shadow-1);
}
/* Inter-block spacing — any top-level container following any other
   top-level container gets var(--gap) margin-top. Replaces the
   ten-rule manual matrix (.card + .card, .grid + .card, etc.) with a
   single :where(...) selector that's 0-specificity, so per-element
   overrides (e.g. inline margin, .grid-N > .card + .card { 0 }) still
   win. The old mistake was that this matrix was incomplete — anything
   not enumerated (e.g. .card + details.disclosure on the Backup page)
   stacked with no gap and looked broken.
   NOTE: .callout also carries its own margin-bottom for legacy
   reasons; the larger of (callout's bottom-margin, next element's
   top-margin) wins via standard margin collapse, so the visible gap
   stays consistent. */
:where(.card, .callout, .grid-2, .grid-3, .grid-4, .hero-strip, details.disclosure)
  + :where(.card, .callout, .grid-2, .grid-3, .grid-4, .hero-strip, details.disclosure) {
  margin-top: var(--gap);
}
/* Cards stacking inside a grid get spacing from the grid's `gap`,
   not from their own margin — explicitly zero out so the :where rule
   above doesn't double up. */
.grid-2 > .card + .card,
.grid-3 > .card + .card,
.grid-4 > .card + .card { margin-top: 0; }

.card-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  min-width: 0;
  gap: 10px;
  margin-bottom: 4px;
}
.card-head h2 {
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  flex-shrink: 0;
  margin: 0;
  letter-spacing: -0.005em;
}
.card-head .right { margin-left: auto; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex-shrink: 0; }
.card-sub {
  color: var(--fg-mute);
  font-size: 12px;
  margin: 0 0 14px;
  line-height: 1.55;
}

.label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--fg-mute);
}

/* ---- forms ---- */

.field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 14px; }
.field-label {
  font-size: 12px;
  font-weight: 500;
  color: var(--fg);
}
.field-help {
  font-size: 11.5px;
  color: var(--fg-mute);
  margin-top: 2px;
}

.input, .textarea, .select {
  width: 100%;
  padding: 8px 10px;
  min-height: 32px;
  box-sizing: border-box;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg);
  font-size: 13px;
  font-family: inherit;
}
[data-admin-theme="light"] .input,
[data-admin-theme="light"] .textarea,
[data-admin-theme="light"] .select {
  background: var(--card);
}
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-ring);
}
.textarea { resize: vertical; min-height: 100px; font-family: var(--font-mono); font-size: 12.5px; }
.input.mono, .textarea.mono { font-family: var(--font-mono); font-size: 12.5px; }

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--fg);
  cursor: pointer;
}
.checkbox-row input[type=checkbox] { accent-color: var(--brand); }

.radio-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}
.radio-row + .radio-row { margin-top: 8px; }
.radio-row.selected { border-color: var(--brand); background: var(--brand-soft); }
.radio-row input { accent-color: var(--brand); margin-top: 2px; }
.radio-row .r-body { flex: 1; }
.radio-row .r-title { font-size: 13px; font-weight: 500; color: var(--fg); }
.radio-row .r-desc { font-size: 12px; color: var(--fg-mute); margin-top: 2px; }

/* ---- chip filter (pill nav) ---- */

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: var(--gap-lg);
}
.chip {
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--fg-dim);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.chip:hover { color: var(--fg); border-color: var(--border-hi); }
.chip.active {
  background: var(--brand-soft);
  color: var(--brand);
  border-color: var(--brand-dim);
}
.chip .chip-count {
  font-family: var(--font-mono);
  font-size: 10.5px;
  background: var(--card-hi);
  color: var(--fg-dim);
  padding: 0 6px;
  border-radius: 999px;
  border: 1px solid var(--border);
}
.chip.active .chip-count {
  background: var(--brand);
  color: var(--fg-on-brand);
  border-color: var(--brand);
}

/* ---- translation review inbox ---- */

.translation-review-head {
  align-items: stretch;
}

.translation-review-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(78px, 1fr));
  gap: 8px;
  flex: 0 0 auto;
}

.translation-stat {
  min-width: 82px;
  padding: 10px 12px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-1);
}

.translation-stat-value {
  display: block;
  font-size: 20px;
  font-weight: 650;
  line-height: 1;
  color: var(--fg);
  letter-spacing: -0.01em;
}

.translation-stat-label {
  display: block;
  margin-top: 5px;
  color: var(--fg-mute);
  font-size: 11px;
  font-weight: 500;
}

.translation-review-filter {
  display: flex;
  align-items: flex-start;
  gap: 18px;
  flex-wrap: wrap;
  padding: 12px 14px;
  margin-bottom: var(--gap-lg);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-1);
}

.translation-invite-panel {
  margin-bottom: var(--gap-lg);
}

.translation-invite-panel .card-head {
  align-items: flex-start;
  justify-content: space-between;
}

.translation-invite-form {
  display: grid;
  gap: 12px;
}

.translation-invite-fields {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 12px;
}

.translation-invite-fields .field {
  margin-bottom: 0;
}

.translation-invite-body {
  min-height: 220px;
  font-family: var(--font-sans);
  font-size: 13px;
}

.translation-invite-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  flex-wrap: wrap;
}

.translation-invite-list {
  padding: 0;
  overflow: hidden;
  margin-bottom: var(--gap-lg);
}

.translation-invite-list .card-head {
  padding: 16px 20px 12px;
  margin: 0;
  border-bottom: 1px solid var(--hairline);
  background: var(--card-2);
}

.translation-invite-rows {
  display: grid;
}

.translation-invite-row {
  display: grid;
  grid-template-columns: minmax(240px, 1.1fr) minmax(260px, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 14px 20px;
  border-top: 1px solid var(--hairline);
}

.translation-invite-row:first-child {
  border-top: none;
}

.translation-invite-row.is-revoked,
.translation-invite-row.is-expired {
  opacity: 0.78;
}

.translation-invite-summary {
  min-width: 0;
}

.translation-invite-name {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  color: var(--fg);
  font-weight: 600;
}

.translation-invite-meta {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 4px;
  color: var(--fg-mute);
  font-size: 11.5px;
}

.translation-invite-meta span + span::before {
  content: "·";
  margin-right: 8px;
  color: var(--fg-faint);
}

.translation-invite-link {
  min-width: 0;
  height: 34px;
  color: var(--fg-dim);
}

.translation-invite-token-error {
  color: var(--fail);
  background: var(--fail-soft);
  border: 1px solid var(--fail-bd);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 12px;
}

.translation-invite-row-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  flex-wrap: wrap;
}

.translation-invite-row-actions .btn {
  min-width: 84px;
}

.translation-filter-group {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.translation-filter-locales {
  flex: 1 1 260px;
}

.translation-chip-row {
  margin: 0;
}

.translation-review-group {
  padding: 0;
  overflow: hidden;
}

.translation-group-head {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  align-items: flex-start;
  padding: 18px 20px 16px;
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border-bottom: 1px solid var(--hairline);
}

.translation-group-title {
  min-width: 0;
}

.translation-group-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.translation-path,
.translation-source-key {
  display: inline-flex;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--fg-mute);
  background: var(--well-deep);
  border: 1px solid var(--hairline);
  border-radius: 5px;
  padding: 2px 6px;
  font-size: 10.5px;
}

.translation-group-head h2 {
  margin: 0;
  font-size: 18px;
  line-height: 1.2;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--fg);
}

.translation-page-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: 12px;
  font-weight: 500;
}

.translation-page-link .icon {
  width: 13px;
  height: 13px;
}

.translation-group-count {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 7px 9px;
  border-radius: 7px;
  background: var(--well-deep);
  border: 1px solid var(--hairline);
  color: var(--fg-mute);
  font-size: 11px;
  white-space: nowrap;
}

.translation-group-count .num {
  color: var(--fg);
  font-weight: 700;
  font-size: 14px;
}

.translation-note-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.translation-note {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
  gap: 18px;
  padding: 18px 20px;
  border-top: 1px solid var(--hairline);
}

.translation-note:first-child {
  border-top: none;
}

.translation-note.is-resolved {
  background: color-mix(in oklab, var(--ok-soft) 34%, transparent);
}

.translation-note-main {
  display: grid;
  gap: 12px;
  min-width: 0;
}

.translation-note-meta {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
  color: var(--fg-mute);
  font-size: 12px;
}

.translation-note-byline {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
}

.translation-status-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--signal);
  box-shadow: 0 0 0 3px var(--signal-soft);
  flex: 0 0 auto;
}

.translation-note.is-resolved .translation-status-dot {
  background: var(--ok);
  box-shadow: 0 0 0 3px var(--ok-soft);
}

.translation-source-key {
  flex: 0 1 auto;
  max-width: min(280px, 100%);
}

.translation-compare {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.translation-text-panel,
.translation-selected-panel {
  min-width: 0;
  padding: 12px;
  background: var(--well-deep);
  border: 1px solid var(--hairline);
  border-radius: 8px;
}

.translation-text-panel.reviewer {
  background: var(--brand-soft);
  border-color: var(--brand-dim);
}

.translation-selected-panel {
  background: var(--signal-soft);
  border-color: var(--signal-ring);
}

.translation-panel-label {
  margin-bottom: 6px;
  color: var(--fg-mute);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.translation-panel-copy {
  color: var(--fg);
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
}

.translation-reviewer-note {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 14px;
  border-left: 3px solid var(--signal);
  border-radius: 8px;
  background: var(--card-2);
  color: var(--fg);
  font-size: 15px;
  line-height: 1.55;
  white-space: pre-wrap;
}

.translation-reviewer-note .icon {
  width: 15px;
  height: 15px;
  margin-top: 3px;
  color: var(--signal);
  flex: 0 0 auto;
}

.translation-resolution-note {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  color: var(--fg-mute);
  font-size: 12px;
}

.translation-resolution-note .icon {
  width: 13px;
  height: 13px;
  margin-top: 2px;
  color: var(--ok);
  flex: 0 0 auto;
}

.translation-note-actions {
  align-self: start;
  display: grid;
  gap: 10px;
  padding: 12px;
  background: var(--card-2);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.translation-resolve-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
  margin: 0;
}

.translation-resolve-form .input {
  min-width: 0;
}

.translation-inline-form {
  margin: 0;
}

.translation-inline-form .btn,
.translation-note-actions > .translation-inline-form,
.translation-note-actions .btn {
  width: 100%;
  justify-content: center;
}

@media (max-width: 1080px) {
  .translation-invite-fields {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .translation-invite-row {
    grid-template-columns: 1fr;
    align-items: stretch;
  }

  .translation-invite-row-actions {
    justify-content: flex-start;
  }

  .translation-note {
    grid-template-columns: 1fr;
  }

  .translation-note-actions {
    grid-template-columns: 1fr auto;
    align-items: center;
  }

  .translation-resolve-form {
    grid-column: 1 / -1;
  }
}

@media (max-width: 720px) {
  .translation-invite-fields {
    grid-template-columns: 1fr;
  }

  .translation-invite-actions {
    justify-content: stretch;
  }

  .translation-invite-actions .btn {
    flex: 1 1 180px;
    justify-content: center;
  }

  .translation-review-stats {
    width: 100%;
  }

  .translation-group-head {
    flex-direction: column;
  }

  .translation-compare {
    grid-template-columns: 1fr;
  }

  .translation-note-actions,
  .translation-resolve-form {
    grid-template-columns: 1fr;
  }
}

/* ---- subscribers filter band ---- */
/* One unified row: status chips on the left, search + selects on the right.
   Wraps cleanly when the viewport narrows. Sits on a card surface so it
   reads as a distinct band against the page bg. */
.subs-filter {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-1);
}
.subs-filter-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.subs-filter-inputs {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-left: auto;
  flex: 1 1 auto;
  min-width: 280px;
  justify-content: flex-end;
}
.subs-filter-inputs .input,
.subs-filter-inputs .select { height: 32px; }
.subs-filter-search { position: relative; flex: 1 1 220px; min-width: 180px; }
.subs-filter-since { flex: 0 0 140px; }

/* Narrow phones (≤480px viewport — flagged at 317px in the live
   sticky-notes inbox). The filter row's children together force a
   row wider than the viewport because each has a min-width. Drop
   the min-widths and let the search box take the full row, then
   the since-select + Apply / Reset / Save tuck under it. */
@media (max-width: 480px) {
  .subs-filter { padding: 10px; gap: 8px; }
  .subs-filter-inputs {
    flex-wrap: wrap;
    min-width: 0;
    width: 100%;
    margin-left: 0;
    justify-content: flex-start;
  }
  .subs-filter-search { flex: 1 1 100%; min-width: 0; }
  .subs-filter-since { flex: 1 1 120px; }
}

/* ---- saved audiences strip ---- */
/* Quieter than the filter band but still on a card surface for clear
   visual separation from the page. Sits between the filter band and
   the table. */
.subs-audiences {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-1);
}
.subs-audiences-label { flex-shrink: 0; }
.subs-audiences-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; flex: 1 1 auto; }
.subs-audiences-chips .chip { white-space: nowrap; }
.subs-audience-name { white-space: nowrap; }
.subs-audiences-link {
  font-size: 11.5px;
  white-space: nowrap;
  flex-shrink: 0;
  margin-left: auto;
}

/* ---- table ---- */

/* Outer wrapper. Gives the table a card-like surface so it reads as
   a distinct block instead of floating on the page.
   overflow-x: auto so wide tables become horizontally scrollable on
   mobile rather than clipping or pushing the page width. The
   border-radius continues to clip column-edge corners cleanly until
   the user scrolls; that's an acceptable cost for not blowing out
   the viewport on iPhone. */
.tbl-wrap {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow-x: auto;
  box-shadow: var(--shadow-1);
}
.tbl {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 12.5px;
}
.tbl thead th {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-mute);
  text-align: left;
  padding: 8px 12px;
  background: var(--card-2);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.tbl thead th.sorted { color: var(--brand); }
.tbl tbody td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--hairline);
  vertical-align: middle;
  color: var(--fg);
  background: var(--card);
}
.tbl tbody tr:hover td { background: var(--card-hi); }
.tbl tbody tr.feedback-row-unseen td {
  background: color-mix(in srgb, var(--brand-soft) 58%, var(--card));
}
.tbl tbody tr.feedback-row-unseen:hover td {
  background: color-mix(in srgb, var(--brand-soft) 70%, var(--card-hi));
}
.tbl tbody tr.feedback-row-unseen td:first-child {
  box-shadow: inset 3px 0 0 var(--brand);
}
.tbl tbody tr:last-child td { border-bottom: none; }
.tbl .right { text-align: right; }
.tbl .num { font-family: var(--font-mono); }
.tbl .mute { color: var(--fg-mute); }

/* ---- callout ---- */

.callout {
  display: flex;
  gap: 10px;
  padding: 12px 14px;
  border-radius: 6px;
  border: 1px solid;
  font-size: 12.5px;
  line-height: 1.55;
  margin-bottom: var(--gap-lg);
}
.callout .icon { width: 16px; height: 16px; flex: 0 0 16px; margin-top: 1px; }
.callout.warn { background: var(--warn-soft); border-color: var(--warn-bd); color: var(--fg); }
.callout.warn .icon { color: var(--warn); }
.callout.fail { background: var(--fail-soft); border-color: var(--fail-bd); color: var(--fg); }
.callout.fail .icon { color: var(--fail); }
.callout.info { background: var(--brand-soft); border-color: var(--brand-dim); color: var(--fg); }
.callout.info .icon { color: var(--brand); }
.callout strong { color: var(--fg); font-weight: 600; }

/* ---- empty state ---- */

.empty {
  text-align: center;
  padding: 32px 20px;
  color: var(--fg-mute);
  font-size: 12.5px;
}
.empty .empty-icon {
  width: 28px; height: 28px;
  margin: 0 auto 10px;
  color: var(--fg-faint);
}
.empty .empty-title {
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-dim);
  margin-bottom: 4px;
}

/* ---- grid helpers ---- */

.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--gap); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap); }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gap); }
.row { display: flex; gap: var(--gap); }

/* Collapse N-column card grids to single column on phone widths.
   Without this the side-by-side .grid-2 cards (e.g. /reports's
   "Bundles by distribution" pair) push the page wider than the
   viewport on iPhone. .grid-3 collapses earlier — three full-width
   cards fit fine, but two-thirds-width is too narrow at tablet. */
@media (max-width: 900px) {
  .grid-3, .grid-4 { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
  .grid-2 { grid-template-columns: 1fr; }
}

/* ---- stat card (unified) ---- */

.stat {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 96px;
}
.stat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.stat-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  color: var(--fg-mute);
  text-transform: uppercase;
  letter-spacing: 0.14em;
}
.stat-value {
  font-family: var(--font-mono);
  font-size: 28px;
  font-weight: 500;
  color: var(--fg);
  line-height: 1;
  margin-top: 6px;
  letter-spacing: -0.01em;
}
.stat-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11.5px;
  color: var(--fg-mute);
  margin-top: auto;
}
.stat-delta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  white-space: nowrap;
  flex-shrink: 0;
  padding: 1px 6px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--card-2);
  color: var(--fg-dim);
}
.stat-delta.up { background: var(--ok-soft); color: var(--ok); border-color: var(--ok-bd); }
.stat-delta.down { background: var(--fail-soft); color: var(--fail); border-color: var(--fail-bd); }

/* split stat — for "Lifetime / 7-day / today" with platform breakdown */
.stat.split .stat-value { font-size: 14px; margin-top: 8px; line-height: 1.5; }

/* clickable stat — entire tile becomes a link to a page */
.stat.clickable { cursor: pointer; transition: background .12s, border-color .12s, transform .06s; }
.stat.clickable:hover { background: var(--card-2); border-color: var(--brand-dim); }
.stat.clickable:active { transform: translateY(1px); }
.stat.clickable:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* tone — left-edge accent so the eye finds the things that need action */
.stat.ok   { box-shadow: inset 3px 0 0 var(--ok); }
.stat.warn { box-shadow: inset 3px 0 0 var(--warn); }
.stat.fail { box-shadow: inset 3px 0 0 var(--fail); }

/* ---- hero strip ---- */
/* Compact horizontal pill row for at-a-glance secondary metrics
   (downloads breakdown, traffic). Less prominent than the .stat grid above
   it, but always visible above the fold. */
.hero-strip {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  overflow: hidden;
  gap: 8px 10px;
  padding: 10px 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.hero-strip:hover { background: var(--card-2); border-color: var(--brand-dim); }
.hero-strip:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.hero-strip .hs-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  color: var(--fg-mute);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  margin-right: 4px;
}
.hero-strip .hs-pill {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  flex-shrink: 0;
  gap: 6px;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--card-2);
  border: 1px solid var(--hairline);
  font-size: 11.5px;
}
.hero-strip .hs-pill.umami {
  background: var(--brand-soft);
  border-color: var(--brand-dim);
}
.hero-strip .hs-k { color: var(--fg-mute); font-size: 10.5px; }
.hero-strip .hs-v { color: var(--fg); font-weight: 600; }
.hero-strip .hs-d { color: var(--fg-faint); font-size: 10.5px; }
.hero-strip .hs-d.up { color: var(--ok); }
.hero-strip .hs-sep {
  width: 1px;
  height: 16px;
  background: var(--hairline);
  margin: 0 2px;
}
.hero-strip .hs-more {
  margin-left: auto;
  display: inline-flex;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex-shrink: 1;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: var(--brand);
}
.hero-strip .hs-more .icon { width: 11px; height: 11px; }

@media (max-width: 720px) {
  .hero-strip .hs-more { display: none; }
  /* At narrow viewports the pill row would otherwise clip silently
     (overflow:hidden + nowrap). Let it wrap onto multiple rows so the
     Downloads/Mail strips stay fully readable on a phone. */
  .hero-strip { flex-wrap: wrap; overflow: visible; }
  .hero-strip .hs-sep { display: none; }
}
.stat.split .stat-row {
  display: flex; justify-content: space-between; align-items: baseline;
  font-family: var(--font-mono); font-size: 13px;
  border-top: 1px solid var(--hairline); padding: 6px 0;
}
.stat.split .stat-row:first-of-type { border-top: none; }
.stat.split .stat-row .k { color: var(--fg-mute); font-size: 11.5px; letter-spacing: 0.04em; }
.stat.split .stat-row .v { color: var(--fg); }
.stat.split .stat-row .v.zero { color: var(--fg-faint); }

/* ---- infra row (key=value with status pill) ---- */

.kv-list { display: flex; flex-direction: column; }
.kv-row {
  display: grid;
  grid-template-columns: 140px 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 10px 0;
  border-top: 1px solid var(--hairline);
}
.kv-row:first-child { border-top: none; }
.kv-row .k {
  font-size: 12.5px;
  color: var(--fg);
  font-weight: 500;
}
.kv-row .v {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--fg-mute);
}

/* ---- audit / activity row ---- */

.feed-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px;
  padding: 10px 0;
  border-top: 1px solid var(--hairline);
  font-size: 12.5px;
}
.feed-row:first-child { border-top: none; }
.feed-row .feed-action {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--fg);
}
.feed-row .feed-target {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--fg-mute);
  margin-left: 6px;
}
.feed-row .feed-meta {
  font-size: 11.5px;
  color: var(--fg-mute);
  margin-top: 2px;
}
.feed-row .feed-time {
  font-size: 11.5px;
  color: var(--fg-faint);
  font-family: var(--font-mono);
  white-space: nowrap;
}

/* ---- bar chart ---- */

.bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 80px;
  padding: 8px 0 4px;
}
.bars .bar {
  flex: 1;
  background: var(--brand-dim);
  border-radius: 2px 2px 0 0;
  min-height: 1px;
  position: relative;
  transition: background 100ms;
}
.bars .bar:hover { background: var(--brand); }
.bars .bar.empty { background: var(--hairline); }
.bars .bar.peak { background: var(--brand); }
.bars-axis {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--fg-faint);
  margin-top: 4px;
  letter-spacing: 0.04em;
}

/* ---- horizontal bar chart (rank list) ---- */

.rank {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.rank-row {
  display: grid;
  grid-template-columns: 140px 1fr 50px;
  gap: 10px;
  align-items: center;
  font-size: 12px;
}
.rank-row .rank-label {
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 11.5px;
}
.rank-row .rank-bar {
  height: 16px;
  background: var(--card-2);
  border-radius: 3px;
  overflow: hidden;
  position: relative;
}
.rank-row .rank-fill {
  height: 100%;
  background: var(--brand);
  opacity: 0.65;
}
.rank-row .rank-num {
  font-family: var(--font-mono);
  text-align: right;
  color: var(--fg-mute);
  font-size: 11.5px;
}

/* ---- in-page nav (sticky toc) ---- */

.toc {
  position: sticky;
  top: 72px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-self: flex-start;
}
.toc a {
  font-size: 12px;
  color: var(--fg-mute);
  padding: 4px 8px;
  border-radius: 4px;
  border-left: 2px solid transparent;
}
.toc a:hover { color: var(--fg); text-decoration: none; }
.toc a.active {
  color: var(--brand);
  border-left-color: var(--brand);
  background: var(--brand-soft);
}

/* ---- tabs ---- */

.tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--gap-lg);
}
.tab {
  padding: 8px 14px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--fg-mute);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
  background: none;
  border-top: none;
  border-left: none;
  border-right: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.tab:hover { color: var(--fg); }
.tab.active {
  color: var(--fg);
  border-bottom-color: var(--brand);
}
.tab .tab-count {
  font-family: var(--font-mono);
  font-size: 10px;
  background: var(--card-2);
  color: var(--fg-mute);
  padding: 0 6px;
  border-radius: 999px;
  border: 1px solid var(--border);
}

/* ---- disclosure ---- */

details.disclosure {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0;
  background: var(--card);
  box-shadow: var(--shadow-1);
}
details.disclosure summary {
  list-style: none;
  cursor: pointer;
  padding: 10px 14px;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--fg-dim);
}
details.disclosure summary::-webkit-details-marker { display: none; }
details.disclosure summary .icon { transition: transform 140ms; }
details[open].disclosure summary .icon { transform: rotate(90deg); }
details.disclosure .disclosure-body {
  padding: 4px 14px 14px;
  border-top: 1px solid var(--hairline);
  color: var(--fg-mute);
  font-size: 12.5px;
  line-height: 1.6;
}
details.disclosure .disclosure-body ul { margin: 6px 0; padding-left: 18px; }
details.disclosure .disclosure-body li { margin: 4px 0; }

/* ---- command palette ---- */

.palette-scrim {
  position: fixed; inset: 0;
  background: var(--overlay);
  z-index: 60;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 12vh;
  backdrop-filter: blur(2px);
}
.palette {
  width: min(620px, 92vw);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-pop);
  overflow: hidden;
}
.palette-search {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--hairline);
  color: var(--fg-mute);
}
.palette-search input {
  flex: 1;
  background: none;
  border: none;
  outline: none;
  color: var(--fg);
  font-size: 14px;
  font-family: inherit;
}
.palette-search input::placeholder { color: var(--fg-faint); }
.palette-body {
  max-height: 60vh;
  overflow-y: auto;
}
.palette-section { padding: 6px 0 8px; }
.palette-h {
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--fg-faint);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  padding: 8px 14px 4px;
}
.palette-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  width: 100%;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  color: var(--fg);
  font-size: 12.5px;
  font-family: inherit;
}
.palette-row:hover, .palette-row:focus-visible {
  background: var(--brand-soft);
  outline: none;
}
.palette-row-body { flex: 1; }
.palette-row-t { color: var(--fg); }
.palette-row-m { font-size: 11px; color: var(--fg-mute); margin-top: 2px; }
.palette-row-kind {
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--fg-faint);
  text-transform: uppercase;
  letter-spacing: 0.14em;
}
.palette-shortcuts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px 16px;
  padding: 4px 14px 12px;
  font-size: 12px;
  color: var(--fg-mute);
}
.palette-shortcuts > div { display: flex; align-items: center; gap: 6px; }

/* ---- tweaks panel positioning ---- */

#tweaks-root { position: fixed; right: 16px; bottom: 16px; z-index: 70; }

/* ---- two-column layout for detail pages ---- */

.col-2 {
  display: grid;
  grid-template-columns: 1fr 280px;
  gap: var(--gap-lg);
  align-items: start;
}
.col-2-rev {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: var(--gap-lg);
  align-items: start;
}
@media (max-width: 900px) {
  .col-2, .col-2-rev { grid-template-columns: 1fr; }
}

/* ---- centered card layout (login etc.) ---- */

.center-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg);
}
.center-card {
  width: min(420px, 100%);
}
.center-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-bottom: 24px;
}

/* ---- mobile ---- */

/* filter grid for the broadcast filter-builder */
.filter-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 14px;
}
@media (max-width: 720px) {
  .filter-grid { grid-template-columns: 1fr; }
}

/* manual subscriber picker */
.picker-list {
  display: flex;
  flex-direction: column;
  max-height: 280px;
  overflow-y: auto;
  border: 1px solid var(--hairline);
  border-radius: 6px;
}
.picker-row {
  display: grid;
  grid-template-columns: 24px 1fr auto auto;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--hairline);
  cursor: pointer;
  font-size: 12.5px;
}
.picker-row:last-child { border-bottom: none; }
.picker-row:hover { background: var(--card-2); }
.picker-row.checked { background: var(--brand-soft); }
.picker-row .picker-email { color: var(--fg); }
.picker-row .picker-meta { font-size: 11px; color: var(--fg-mute); }

/* small pill — for inline contexts where a normal pill is too tall */
.pill.sm {
  font-size: 10px;
  padding: 1px 6px;
}

/* In feed-row contexts (dashboard "Recent signups" + "Last 24 hours",
   broadcast/search/subscriber-detail activity lists) the filled-pill
   chrome reads as a button, not a status. Drop the background/border
   chrome and prepend a small colored dot — same status colors via
   currentColor — so the row reads "label · status" instead of
   "row with a clickable thing". */
.feed-row .pill {
  background: transparent !important;
  border: none !important;
  padding: 0;
  text-transform: none;
  letter-spacing: 0.01em;
  font-weight: 500;
  font-size: 11.5px;
  font-family: var(--font-sans);
}
.feed-row .pill::before {
  content: '';
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 6px;
  border-radius: 50%;
  background: currentColor;
  vertical-align: middle;
}
.feed-row .pill.sm {
  font-size: 11px;
  padding: 0;
}
.feed-row .pill.sm::before {
  width: 6px;
  height: 6px;
  margin-right: 5px;
}

/* ---- dropzone (backup restore upload) ---- */
.dropzone {
  display: block;
  padding: 24px 20px;
  border: 1.5px dashed var(--border);
  border-radius: 8px;
  background: var(--card-2);
  text-align: center;
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
.dropzone:hover {
  border-color: var(--brand);
  background: var(--brand-soft);
}
.dropzone .dz-icon {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--card);
  border: 1px solid var(--hairline);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--brand);
  margin-bottom: 8px;
}
.dropzone .dz-title {
  font-size: 13px;
  font-weight: 500;
  color: var(--fg);
  margin-bottom: 4px;
}
.dropzone .dz-meta {
  font-size: 11.5px;
  color: var(--fg-mute);
}

/* ---- restore steps ---- */
.restore-steps {
  list-style: none;
  margin: var(--gap) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}
.restore-steps li {
  display: grid;
  grid-template-columns: 32px 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--hairline);
  opacity: 0.55;
}
.restore-steps li:first-child { border-top: none; }
.restore-steps li.done   { opacity: 1; }
.restore-steps li.active { opacity: 1; }
.restore-steps .step-num {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--card-2);
  border: 1px solid var(--border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--fg-mute);
}
.restore-steps li.done .step-num {
  background: var(--ok-soft);
  border-color: var(--ok-bd);
  color: var(--ok);
}
.restore-steps li.active .step-num {
  background: var(--brand-soft);
  border-color: var(--brand-dim);
  color: var(--brand);
}
.restore-steps .step-title {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--fg);
}
.restore-steps .step-meta {
  font-size: 11px;
  color: var(--fg-mute);
  margin-top: 1px;
}

/* grid-4 collapses earlier than other grids — 4 hero stats on one row needs space */
@media (max-width: 1100px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
}

/* ---- bulk action bar ---- */
/* Floating bar that appears when ≥1 table row is selected. Sits at the bottom
   of the viewport over the main content. */
.bulk-bar {
  position: sticky;
  bottom: 16px;
  margin-top: var(--gap);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: var(--card);
  border: 1px solid var(--brand-dim);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18), 0 1px 0 var(--hairline);
  flex-wrap: wrap;
  z-index: 5;
}
.bulk-bar .bulk-count {
  font-size: 12px;
  font-weight: 600;
  color: var(--brand);
  padding-right: 4px;
}
.bulk-bar .bulk-sep {
  width: 1px;
  height: 18px;
  background: var(--hairline);
  margin: 0 2px;
}

/* selected row */
.tbl tr.selected td {
  background: var(--brand-soft);
}

/* ---- modal ---- */
.modal-scrim {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: 24px;
}
.modal {
  width: min(520px, 100%);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 16px 48px rgba(0,0,0,0.36);
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 48px);
  overflow: hidden;
}
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--hairline);
}
.modal-head h2 { margin: 0; font-size: 14px; font-weight: 600; color: var(--fg); }
.modal-body {
  padding: 18px;
  overflow-y: auto;
}
.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 18px;
  border-top: 1px solid var(--hairline);
  background: var(--card-2);
}

.mobile-trigger { display: none; }

@media (max-width: 900px) {
  .app {
    grid-template-columns: 1fr;
    grid-template-rows: 56px 1fr;
    grid-template-areas: "topbar" "main";
  }
  .topbar { left: 0; }
  .rail {
    position: fixed;
    top: 0; left: 0;
    width: 280px;
    height: 100vh;
    transform: translateX(-100%);
    transition: transform 180ms ease;
    z-index: 50;
    box-shadow: var(--shadow-pop);
  }
  .rail.open { transform: translateX(0); }
  .rail-scrim {
    position: fixed; inset: 0;
    background: var(--overlay);
    z-index: 40;
    opacity: 0; pointer-events: none;
    transition: opacity 180ms ease;
  }
  .rail-scrim.show { opacity: 1; pointer-events: auto; }
  .mobile-trigger { display: inline-flex; }
  .topbar-search { max-width: none; }
  .main { padding: 20px 16px 60px; max-width: none; }
  .grid-4, .grid-3, .grid-2 { grid-template-columns: 1fr; }
  .page-head { flex-wrap: wrap; }
  .tbl-wrap { overflow-x: auto; }
}


/* ---- web traffic page utilities ---- */

/* live indicator pulse */
.live-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 0 color-mix(in oklab, var(--ok) 40%, transparent);
  animation: live-pulse 2s ease-out infinite;
  vertical-align: middle;
  flex-shrink: 0;
}
@keyframes live-pulse {
  0%   { box-shadow: 0 0 0 0   color-mix(in oklab, var(--ok) 50%, transparent); }
  70%  { box-shadow: 0 0 0 6px color-mix(in oklab, var(--ok) 0%,  transparent); }
  100% { box-shadow: 0 0 0 0   color-mix(in oklab, var(--ok) 0%,  transparent); }
}

/* tiny chart legend */
.legend {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--fg-mute);
  font-family: var(--font-mono);
}
.legend-sw {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  background: var(--brand);
  display: inline-block;
}

/* annotation hanging off a bar chart */
.bars-callout {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  padding: 8px 12px;
  background: var(--brand-soft);
  border: 1px solid var(--brand-dim);
  border-radius: 6px;
  font-size: 12px;
  color: var(--fg);
}
.bars-callout svg { color: var(--brand); flex-shrink: 0; }
.bars-callout a { color: var(--brand); }

/* secondary descriptor on rank label */
.rank-hint {
  color: var(--fg-faint);
  font-size: 10.5px;
  margin-left: 6px;
  font-family: var(--font-sans);
}

/* foot row tucked under a card */
.card-foot {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
  font-size: 11.5px;
  color: var(--fg-mute);
  line-height: 1.55;
}

/* page-level closing footnote */
.page-foot {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: var(--gap-lg);
  padding: 12px 14px;
  background: var(--card-2);
  border: 1px solid var(--hairline);
  border-radius: 6px;
  font-size: 11.5px;
  color: var(--fg-mute);
  line-height: 1.55;
}
.page-foot svg { color: var(--fg-faint); flex-shrink: 0; margin-top: 2px; }
.page-foot .num { color: var(--fg); }

/* ============================================================
   Razor-port additions (not in the original prototype tokens.css)
   ============================================================ */

/* Recipients-picker tabs on /admin/broadcast.
   Three radios drive both the visible-tab state AND the form's
   `targetMode` value. Pure CSS toggle — no JS, CSP-clean. */
.recipients-area .picker-radio-toggle {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.recipients-area .picker-pane { display: none; }
.recipients-area #pm-saved:checked  ~ .card .saved-pane,
.recipients-area #pm-filter:checked ~ .card .filter-pane,
.recipients-area #pm-manual:checked ~ .card .manual-pane,
.recipients-area #pm-list:checked   ~ .card .list-pane { display: block; }

/* The Compose card follows .recipients-area in flow; without an explicit
   margin it touches awkwardly because .card + .card doesn't apply across
   the wrapper. Match the inter-card spacing other pages use. */
.recipients-area + .card { margin-top: var(--gap); }
.recipients-area .picker-tabs label.tab {
  cursor: pointer;
}
.recipients-area #pm-saved:checked  ~ .card .picker-tabs label[for="pm-saved"],
.recipients-area #pm-filter:checked ~ .card .picker-tabs label[for="pm-filter"],
.recipients-area #pm-manual:checked ~ .card .picker-tabs label[for="pm-manual"],
.recipients-area #pm-list:checked   ~ .card .picker-tabs label[for="pm-list"] {
  color: var(--fg);
  border-bottom-color: var(--brand);
}

/* Action tile — used in Subscriber/Feedback detail "Actions" cards
   to wrap a destructive button with explanatory text below.
   Each tile sits inside a .grid-3 container. */
.action-tile {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--hairline);
  border-radius: 6px;
  background: var(--card-2);
}
.action-tile .btn { width: 100%; justify-content: center; }
.action-tile-hint {
  font-size: 11.5px;
  color: var(--fg-mute);
  line-height: 1.55;
}

/* Pager (used by Components/Shared/AdminPager.razor on list pages).
   Class names match admin.css for component reuse — the Razor pager
   doesn't need a v2-specific variant. */
.admin-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 0;
  margin-top: var(--gap);
  font-size: 12px;
  color: var(--fg-mute);
}
.admin-pager-info { font-family: var(--font-mono); font-size: 11.5px; }
.admin-pager-links { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; }
.admin-pager-gap { padding: 0 4px; color: var(--fg-faint); }
.admin-pager .admin-btn {
  display: inline-flex; align-items: center;
  padding: 4px 9px; min-height: 24px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--card);
  color: var(--fg-dim);
  font-size: 11.5px;
  font-family: var(--font-mono);
  text-decoration: none;
}
.admin-pager .admin-btn:hover { background: var(--card-hi); color: var(--fg); text-decoration: none; }
.admin-pager-current {
  background: var(--brand-soft) !important;
  color: var(--brand) !important;
  border-color: var(--brand-dim) !important;
}

/* Bulk-action bar visibility — admin-bulk.js stamps .has-selection on
   the form when ≥1 row checkbox is on. Hidden until then. */
form.admin-bulk-form .bulk-bar { display: none; }
form.admin-bulk-form.has-selection .bulk-bar { display: flex; }

/* (Was: focus-suppression rules for the page-head h1 to defeat Blazor's
    FocusOnNavigate. The real fix is in Routes.razor — FocusOnNavigate
    was removed there, so no programmatic focus lands on h1s anymore.) */

/* Bars chart safety — keep the chart inside its card no matter how many
   bars are rendered. The 30-bar + flex:1 layout normally fits, but with
   tighter density or an unusually small card the flex children could
   overflow without these guards. */
.bars { width: 100%; overflow: hidden; }
.bars .bar { min-width: 0; }

/* Screen-reader-only utility (button labels, etc.) */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Pure-CSS mobile drawer driven by a hidden checkbox + sibling combinator,
   matching the existing site-wide pattern (admin-drawer-toggle, nav-open,
   docs-toc-toggle). The checkbox is a sibling of .app; CSS uses :has()
   to flip the rail's transform when the checkbox is checked.
   Replaces the prototype's JS-driven .rail.open class so we don't
   regress on the strict-CSP "script-src 'self'" admin requirement. */
.adminv2-drawer-toggle { position: absolute; opacity: 0; pointer-events: none; }
.adminv2-drawer-backdrop {
  display: none;
  position: fixed; inset: 0;
  background: var(--overlay);
  z-index: 40;
}
@media (max-width: 900px) {
  /* When the toggle is checked: rail slides in, backdrop appears.
     The toggle checkbox lives as a SIBLING of .adminv2-shell in the
     Razor markup (see AdminV2Shell.razor) — it has to, because
     making it a descendant would put a non-grid-item child inside
     the grid container and warp the layout. So we use the sibling
     combinator (~) to reach into the shell from the checkbox's
     :checked state. The earlier :has() form silently failed
     because :has() walks DESCENDANTS, not siblings. */
  .adminv2-drawer-toggle:checked ~ .adminv2-shell .rail {
    transform: translateX(0);
  }
  .adminv2-drawer-toggle:checked ~ .adminv2-shell .adminv2-drawer-backdrop {
    display: block;
  }
}

/* ---- font scaling on large viewports ------------------------------ */
/* The admin's base type was tuned for 1080–1440 desktops. On 1920+/4K
   monitors the chrome reads small. Three breakpoints bump body type
   and the high-impact chrome selectors in lockstep with the .main
   max-width breakpoints above so the page stays balanced. Lists and
   tables inherit from body; sizes that are explicitly tuned (.btn,
   .pill, .stat-*) get a coordinated bump here.

   Selectors mirror the original declarations rather than introducing
   a CSS-variable indirection, since most of the CSS uses absolute
   px and refactoring everything to use a multiplier would be a much
   larger rewrite. If the small-px values change anywhere above, mirror
   the change here too. */

@media (min-width: 1700px) {
  html, body { font-size: 14px; }
  .page-head h1 { font-size: 24px; }
  .card-head h2 { font-size: 15px; }
  .stat-value { font-size: 32px; }
  .stat-label { font-size: 10.5px; }
  .stat-foot { font-size: 12.5px; }
  .stat-delta { font-size: 11px; }
  .btn { font-size: 13px; }
  .btn.sm { font-size: 12px; }
  .pill { font-size: 11px; }
  .pill.sm { font-size: 10.5px; }
  .tbl thead th { font-size: 11.5px; }
  .needs-reply-text { font-size: 14px; }
  .needs-reply-meta, .needs-reply-time { font-size: 12px; }
  .feed-row .feed-action { font-size: 13px; }
  .feed-row .feed-meta { font-size: 11.5px; }
  .hs-pill { font-size: 11.5px; }
}

@media (min-width: 2100px) {
  html, body { font-size: 15px; }
  .page-head h1 { font-size: 26px; }
  .card-head h2 { font-size: 16px; }
  .stat-value { font-size: 34px; }
  .stat-label { font-size: 11px; }
  .stat-foot { font-size: 13px; }
  .stat-delta { font-size: 11.5px; }
  .btn { font-size: 13.5px; }
  .btn.sm { font-size: 12.5px; }
  .pill { font-size: 11.5px; }
  .pill.sm { font-size: 11px; }
  .tbl thead th { font-size: 12px; }
  .needs-reply-text { font-size: 15px; }
  .needs-reply-meta, .needs-reply-time { font-size: 12.5px; }
  .feed-row .feed-action { font-size: 14px; }
  .feed-row .feed-meta { font-size: 12px; }
  .hs-pill { font-size: 12px; }
}

@media (min-width: 2400px) {
  html, body { font-size: 16px; }
  .page-head h1 { font-size: 28px; }
  .card-head h2 { font-size: 18px; }
  .stat-value { font-size: 36px; }
  .stat-label { font-size: 11.5px; }
  .stat-foot { font-size: 13.5px; }
  .stat-delta { font-size: 12px; }
  .btn { font-size: 14px; }
  .btn.sm { font-size: 13px; }
  .pill { font-size: 12px; }
  .pill.sm { font-size: 11.5px; }
  .tbl thead th { font-size: 12.5px; }
  .needs-reply-text { font-size: 16px; }
  .needs-reply-meta, .needs-reply-time { font-size: 13px; }
  .feed-row .feed-action { font-size: 15px; }
  .feed-row .feed-meta { font-size: 12.5px; }
  .hs-pill { font-size: 12.5px; }
}
