/* ============================================================================
   20. App shell — spine (the reference SaaS chrome)
   ----------------------------------------------------------------------------
   Protected in-app pages render the full app shell instead of the marketing
   top navbar: a 214px sidebar spine (brand head, grouped sections, live
   counts, locked rows, admin chips), the content column, and — on mobile,
   where the spine is hidden — a bottom tab bar instead. There is no top bar
   and no separate identity rail; the sidebar is the only chrome.
   ========================================================================== */

.app-shell {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
}

/* ---- 20b. Shell pane: spine + content ----------------------------------- */

.shell-pane {
  display: flex;
  flex: 1;
  min-height: 0;
  align-items: stretch;
}

/* ---- 20d. Sidebar spine (214px) ---------------------------------------- */

.app-sidebar {
  flex: none;
  width: 214px;
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--ta-border);
  background: var(--ta-surface);
}

.sidebar-head {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.875rem 0.875rem 0.5rem;
  /* The sidebar head is a link back to the app home — keep the brand looking
     like a brand, not a link. */
  color: inherit;
  text-decoration: none;
}

.sidebar-mark {
  display: grid;
  place-items: center;
  flex: none;
}

.sidebar-mark svg {
  width: 1.5rem;
  height: 1.5rem;
}

.sidebar-logo {
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  object-fit: contain;
}

.sidebar-head-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.sidebar-app {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ta-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sidebar-org {
  font-size: 0.625rem;
}

.sidebar-search {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.25rem 0.625rem 0.625rem;
  padding: 0.375rem 0.625rem;
  border-radius: var(--ta-r-sm);
  border: 1px solid var(--ta-border);
  background: var(--ta-bg);
  color: var(--ta-muted);
  font-size: 0.8125rem;
  cursor: pointer;
  text-align: left;
  transition: border-color var(--ta-t) var(--ta-ease);
}

.sidebar-search:hover {
  border-color: var(--ta-border-strong);
}

.sidebar-search-icon svg {
  width: 0.9375rem;
  height: 0.9375rem;
}

.sidebar-search span:nth-child(2) {
  flex: 1;
}

.sidebar-search kbd {
  font-family: var(--ta-mono);
  font-size: 0.625rem;
  color: var(--ta-faint);
  border: 1px solid var(--ta-border);
  border-radius: 0.25rem;
  padding: 0 0.25rem;
  background: var(--ta-surface);
}

.sidebar-nav {
  flex: 1;
  overflow-y: auto;
  padding: 0.25rem 0.625rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.nav-section-label {
  padding: 0.875rem 0.5rem 0.375rem;
  font-size: 0.625rem;
}

.nav-sections {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.nav-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.4375rem 0.5rem;
  border-radius: var(--ta-r-sm);
  color: var(--ta-muted);
  font-size: 0.875rem;
  text-decoration: none;
  transition: background var(--ta-t) var(--ta-ease), color var(--ta-t) var(--ta-ease);
}

.nav-row:hover {
  background: var(--ta-surface-2);
  color: var(--ta-text);
}

.nav-row.active {
  background: var(--ta-accent-soft);
  color: var(--ta-accent-ink, var(--ta-accent));
  font-weight: 500;
}

.nav-row.locked {
  color: var(--ta-faint);
}

.nav-row.locked:hover {
  background: transparent;
  color: var(--ta-faint);
}

.nav-row-icon {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--ta-faint);
}

.nav-row.active .nav-row-icon {
  color: var(--ta-accent-ink, var(--ta-accent));
}

.nav-row-icon svg {
  width: 1rem;
  height: 1rem;
}

.nav-row-label {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-row-count {
  font-family: var(--ta-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.6875rem;
  color: var(--ta-faint);
}

.nav-row.active .nav-row-count {
  color: var(--ta-accent-ink, var(--ta-accent));
}

.nav-row-lock svg {
  width: 0.875rem;
  height: 0.875rem;
}

.nav-row-admin {
  font-family: var(--ta-mono);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.125rem 0.375rem;
  border-radius: 0.25rem;
  background: var(--ta-surface-2);
  color: var(--ta-faint);
}

.sidebar-footer {
  border-top: 1px solid var(--ta-border);
  padding: 0.5rem 0.625rem;
}

.sidebar-footer .nav-row {
  color: var(--ta-muted);
}

/* ---- 20e. Content column ------------------------------------------------ */

.app-main {
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
}

/* Any direct child of the content column is a flex item with `min-width:
   auto` by default, so a wide table, form row or card grid forced the column
   wider than the pane on small screens — and the app-shell's `overflow-x:
   hidden` clipped the right edge instead of scrolling. Let every child shrink
   to the column; wide `.table-scroll` tables keep scrolling in-band, and the
   cards/forms that need truncation carry their own `min-w-0`/truncate
   internally. */
.app-main>* {
  min-width: 0;
}

/* The content column's padding, as custom properties so the bleed rules below
   negate exactly what was applied rather than a copy that can drift.
   Deliberately over-specific: the app shell shares the `container` class with
   marketing pages, and base.css's `main.container:not(:has(> nav.topbar))`
   (0,2,2) out-specifies a plain `.app-main`, so the top value has to be
   reasserted from a selector that outranks it. 2.5rem matches what that rule
   was already applying here — this fixes where the value comes from, not what
   the app looks like. */
.app-shell .shell-pane .app-main {
  --app-pad-x: 2rem;
  --app-pad-y: 2.5rem;
  padding: var(--app-pad-y) var(--app-pad-x) 3rem;
}

/* A direct block child opts into this to break out of the column's horizontal
   padding and span edge-to-edge — flush against the sidebar on one side and
   the viewport edge on the other — without escaping the sidebar itself. */
.app-main>.bleed {
  margin-inline: calc(-1 * var(--app-pad-x));
  width: calc(100% + 2 * var(--app-pad-x));
  /* A full-bleed block needs room before whatever follows it — e.g. the
     overview's ops map would otherwise sit flush against the events table. */
  margin-bottom: var(--app-pad-y);
}

/* Opening the page with a bleed element means it meets the top of the pane
   too — otherwise the column's top padding leaves a strip of page background
   above it that reads as a gap. Only the first child: a bleed element further
   down still wants its rhythm against whatever precedes it. */
.app-main>.bleed:first-child {
  margin-top: calc(-1 * var(--app-pad-y));
}

/* ---- 20f. Mobile bottom nav --------------------------------------------- */

.mobile-bottom-nav {
  display: none;
}

@media (max-width: 1024px) {
  .app-sidebar {
    display: none;
  }

  .app-shell .shell-pane .app-main {
    --app-pad-x: 1.125rem;
    padding: var(--app-pad-y) var(--app-pad-x) 4.5rem;
  }

  /* The reference mobile pattern: the first four primary links as bottom
     tabs, plus a trailing "More" tab that opens a sheet with everything
     else — remaining links, grouped sections, and Log out. The sidebar
     (and its footer Log out row) is hidden at this width, so the sheet is
     the only way to reach them on a small screen. */
  .mobile-bottom-nav {
    display: flex;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 45;
    height: 3.5rem;
    background: var(--ta-surface);
    border-top: 1px solid var(--ta-border);
    padding-bottom: env(safe-area-inset-bottom);
  }

  .mbn-item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.1875rem;
    border: none;
    background: transparent;
    font: inherit;
    font-size: 0.6875rem;
    color: var(--ta-muted);
    text-decoration: none;
    cursor: pointer;
  }

  .mbn-icon svg {
    width: 1.125rem;
    height: 1.125rem;
  }

  .mbn-item.active {
    color: var(--ta-accent-ink, var(--ta-accent));
    font-weight: 500;
  }
}

/* ---- 20g. Mobile "More" sheet -------------------------------------------- */

.mobile-more-sheet {
  position: fixed;
  inset: 0;
  z-index: 65;
  display: grid;
  align-items: end;
}

.mobile-more-sheet[hidden] {
  display: none;
}

.mobile-more-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(9, 12, 17, 0.5);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

.mobile-more-panel {
  position: relative;
  width: 100%;
  max-height: min(70vh, 32rem);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--ta-r-xl) var(--ta-r-xl) 0 0;
  box-shadow: var(--ta-shadow-xl);
  padding-bottom: env(safe-area-inset-bottom);
}

.mobile-more-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--ta-border);
}

.mobile-more-x {
  border: none;
  background: transparent;
  color: var(--ta-muted);
  font-size: 1.25rem;
  cursor: pointer;
  line-height: 1;
}

.mobile-more-list {
  overflow-y: auto;
  padding: 0.5rem;
}

.mobile-more-footer {
  border-top: 1px solid var(--ta-border);
  padding: 0.5rem;
}

/* ---- 20h. "Not in your plan" lock sheet --------------------------------- */

.plan-lock {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 1rem;
}

.plan-lock[hidden] {
  display: none;
}

.plan-lock-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(9, 12, 17, 0.5);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

.plan-lock-sheet {
  position: relative;
  width: min(480px, 100%);
  padding: 1.25rem 1.5rem 1.5rem;
  border-radius: var(--ta-r-xl);
  box-shadow: var(--ta-shadow-xl);
}

.plan-lock-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.75rem;
}

.plan-lock-x {
  border: none;
  background: transparent;
  color: var(--ta-muted);
  font-size: 1.25rem;
  cursor: pointer;
  line-height: 1;
}

.plan-lock-body h3 {
  margin: 0 0 0.5rem;
  font-size: 1.125rem;
}

.plan-lock-body p {
  margin: 0;
  color: var(--ta-muted);
  line-height: 1.6;
}

.plan-lock-actions {
  display: flex;
  gap: 0.625rem;
  margin-top: 1.25rem;
}

/* ---- 20h. Global search palette (⌘K) ------------------------------------ */

.search-overlay {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  place-items: start center;
  padding: 12vh 1rem 1rem;
}

.search-overlay[hidden] {
  display: none;
}

.search-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(9, 12, 17, 0.45);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

.search-palette {
  position: relative;
  width: min(560px, 100%);
  max-height: min(60vh, 480px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--ta-r-xl);
  box-shadow: var(--ta-shadow-xl);
}

.search-input-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--ta-border);
}

.search-glyph {
  color: var(--ta-muted);
  font-size: 1.05rem;
}

.search-input-row input {
  flex: 1;
  border: none;
  outline: none;
  background: transparent;
  color: var(--ta-text);
  font: inherit;
  font-size: 1rem;
}

.search-input-row kbd {
  font-family: var(--ta-mono);
  font-size: 0.625rem;
  color: var(--ta-faint);
  border: 1px solid var(--ta-border);
  border-radius: 0.25rem;
  padding: 0.125rem 0.375rem;
  background: var(--ta-surface);
}

.search-results {
  overflow-y: auto;
  padding: 0.5rem;
}

.search-group {
  padding: 0.625rem 0.625rem 0.25rem;
  font-size: 0.625rem;
}

.search-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.625rem;
  border-radius: var(--ta-r-sm);
  color: var(--ta-text);
  text-decoration: none;
  font-size: 0.875rem;
}

.search-row:hover,
.search-row.active {
  background: var(--ta-accent-soft);
  color: var(--ta-accent-ink, var(--ta-accent));
}

.search-row-label {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.search-row-hint {
  font-family: var(--ta-mono);
  font-size: 0.6875rem;
  color: var(--ta-faint);
}

.search-empty {
  padding: 1rem 0.625rem;
  color: var(--ta-muted);
  font-size: 0.875rem;
}

/* ============================================================================
   20k. App shell + dashboard refinements (2026-08-10)
   ----------------------------------------------------------------------------
   Push-beyond pass over the reference spine and the metric dashboard: a
   wider rail with crafted active states and count chips, a proper account
   footer with a manual theme toggle, a consistent page head, a 12-column
   dashboard grid (legacy span 8 + span 4 chart pairing), KPI stat cards,
   and denser in-app charts/tables. Loaded near the end of the cascade so
   these overrides win over the base landing/dashboard/tables rules.
   ========================================================================== */

/* ---- Rail: a little more air -------------------------------------------- */

.app-sidebar {
  width: 224px;
}

.sidebar-head {
  padding: 1rem 0.9375rem 0.625rem;
  gap: 0.6875rem;
}

.sidebar-logo {
  width: 1.875rem;
  height: 1.875rem;
}

.sidebar-app {
  font-size: 0.9375rem;
  font-weight: 650;
  letter-spacing: -0.012em;
}

.sidebar-org {
  font-size: 0.6875rem;
  color: var(--ta-faint);
}

/* Search trigger: soft fill, refined. */
.sidebar-search {
  margin: 0.375rem 0.75rem 0.75rem;
  padding: 0.5rem 0.625rem;
  border-radius: var(--ta-r);
  border-color: transparent;
  background: var(--ta-surface-2);
  font-weight: 500;
  transition: border-color var(--ta-t) var(--ta-ease),
    background var(--ta-t) var(--ta-ease);
}

.sidebar-search:hover {
  background: var(--ta-bg);
  border-color: var(--ta-border);
}

.sidebar-search:active {
  background: var(--ta-surface);
}

.sidebar-search kbd {
  border-color: var(--ta-border);
  box-shadow: inset 0 -1px 0 var(--ta-border);
}

/* ---- Nav rows: crafted active + count chips ------------------------------ */

.nav-row {
  position: relative;
  padding: 0.5rem 0.5625rem;
  border-radius: var(--ta-r);
}

.nav-row::before {
  content: "";
  position: absolute;
  left: 0.125rem;
  top: 50%;
  width: 0.1875rem;
  height: 55%;
  border-radius: 0.1875rem;
  background: var(--ta-accent);
  opacity: 0;
  transform: translateY(-50%) scaleY(0.3);
  transition: opacity var(--ta-t) var(--ta-ease),
    transform var(--ta-t) var(--ta-ease);
}

.nav-row:hover {
  background: var(--ta-surface-2);
}

.nav-row.active {
  background: var(--ta-accent-soft);
  color: var(--ta-accent-ink, var(--ta-accent));
  font-weight: 550;
}

.nav-row.active:hover {
  background: var(--ta-accent-soft);
}

.nav-row.active::before {
  opacity: 1;
  transform: translateY(-50%) scaleY(1);
}

.nav-row:focus-visible {
  outline: 2px solid var(--ta-accent);
  outline-offset: -2px;
}

.nav-row-count {
  color: var(--ta-muted);
}

.nav-row.active .nav-row-count {
  color: var(--ta-accent-ink, var(--ta-accent));
}

.nav-row-lock svg {
  width: 0.9375rem;
  height: 0.9375rem;
}

.nav-section-label {
  padding-top: 1rem;
}

/* ---- Account footer + theme toggle --------------------------------------- */

.sidebar-footer {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.5rem 0.625rem 0.625rem;
}

.sidebar-account {
  display: flex;
  align-items: center;
  gap: 0.5625rem;
  padding: 0.375rem 0.4375rem;
  border-radius: var(--ta-r);
}

.sidebar-avatar {
  flex: none;
  width: 1.875rem;
  height: 1.875rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: var(--ta-accent-soft);
  color: var(--ta-accent-ink, var(--ta-accent));
  font-size: 0.6875rem;
  font-weight: 650;
  letter-spacing: 0.02em;
}

.sidebar-account-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
  line-height: 1.25;
}

.sidebar-account-name {
  font-size: 0.8125rem;
  font-weight: 550;
  color: var(--ta-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sidebar-account-role {
  font-size: 0.6875rem;
  color: var(--ta-faint);
}

/* The manual theme toggle: one button, three states (system → light → dark).
   The current state's icon is revealed via data-mode on the button. */
.theme-toggle {
  flex: none;
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--ta-border);
  border-radius: var(--ta-r);
  background: var(--ta-surface);
  color: var(--ta-muted);
  cursor: pointer;
  transition: color var(--ta-t) var(--ta-ease),
    border-color var(--ta-t) var(--ta-ease),
    background var(--ta-t) var(--ta-ease);
}

.theme-toggle:hover {
  color: var(--ta-text);
  border-color: var(--ta-border-strong);
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--ta-accent);
  outline-offset: 2px;
}

.theme-toggle svg {
  width: 1rem;
  height: 1rem;
}

.theme-toggle .theme-icon {
  display: none;
}

.theme-toggle[data-mode="system"] .theme-icon-system,
.theme-toggle[data-mode="light"] .theme-icon-light,
.theme-toggle[data-mode="dark"] .theme-icon-dark {
  display: block;
}

.sidebar-footer .nav-row.sidebar-settings {
  color: var(--ta-muted);
}

/* ---- Dashboard grid (metric pages) ---------------------------------------- */

/* Pages that open with a KPI row (a dashboard) flow their sections through a
   12-column grid — KPI row full width, charts at their declared span (the
   legacy span 8 + span 4 pairing), tables full width. Every other app page
   keeps the plain stacked column. */
main.app-main:has(> section.kpi-row) {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 1.25rem 1.5rem;
  align-items: start;
  /* Rows take their content height and stack from the top. Without this the
     implicit rows stretch to fill the pane, so a dashboard with only a header
     and a KPI row spreads them to the top and middle of the screen instead of
     stacking them under the header. */
  align-content: start;
  /* The default `auto` track sizing collapses the KPI row's implicit row to
     near 0 (its `overflow: hidden` clipping pane throws off Chrome's
     max-content pass) — the chart sections after it then start at the KPI
     row's own top edge and render straight through it. `min-content` sizes
     every implicit row to its tallest item's real content, same outcome as
     `auto` was supposed to give. */
  grid-auto-rows: min-content;
}

main.app-main:has(> section.kpi-row)>* {
  grid-column: 1 / -1;
  min-width: 0;
}

main.app-main:has(> section.kpi-row)>.kpi-row {
  grid-column: 1 / -1;
  margin-block: 0;
}

main.app-main:has(> section.kpi-row)>.chart.span-8 {
  grid-column: span 8;
}

main.app-main:has(> section.kpi-row)>.chart.span-4 {
  grid-column: span 4;
}

main.app-main:has(> section.kpi-row)>section {
  margin-block: 0;
}

/* In-app chart sections sit tighter than the marketing rhythm. */
.app-main .chart {
  margin-block: 0 1.75rem;
}

main.app-main:has(> section.kpi-row)>.chart {
  margin-block: 0;
  /* The grid's align-items: start (above) keeps content-height rows for the
     common case, but it means the span-8/span-4 chart pair — sitting side
     by side in the same row — don't match height when one has a caption
     and the other doesn't. Stretch just this pair to the row's height and
     let the card absorb the slack. */
  align-self: stretch;
  display: flex;
  flex-direction: column;
}

main.app-main:has(> section.kpi-row)>.chart>.chart-body {
  display: flex;
  flex-direction: column;
  flex: 1;
}

main.app-main:has(> section.kpi-row)>.chart .chart-card {
  flex: 1;
}

/* ---- Paired dials ---------------------------------------------------------
 * Two adjacent gauge sections (e.g. a record-scoped device page's "load" +
 * "storage" read-out) sit side-by-side as a paired status card instead of two
 * full-width dials stacked on top of each other. Everything else on the page
 * (the record-detail, related lists) still spans the full row below them. */
main.app-main:has(> section.gauge + section.gauge) {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem 1.5rem;
  align-items: start;
  align-content: start;
  grid-auto-rows: min-content;
}

main.app-main:has(> section.gauge + section.gauge)>* {
  grid-column: 1 / -1;
  min-width: 0;
  margin-block: 0;
}

main.app-main:has(> section.gauge + section.gauge)>section.gauge {
  grid-column: span 1;
  margin-block: 0;
}

@media (max-width: 720px) {
  main.app-main:has(> section.gauge + section.gauge) {
    grid-template-columns: 1fr;
  }

  main.app-main:has(> section.gauge + section.gauge)>section.gauge {
    grid-column: 1 / -1;
  }
}

/* ---- KPI row -------------------------------------------------------------
 * ONE panel, not a row of cards. The reference draws a headline row of numbers
 * as a single bordered pane: a header naming the set, then the tiles inside it
 * held apart by 1px rules rather than by gaps. Four detached cards read as four
 * unrelated widgets; one divided pane reads as one measurement of one thing. */

.kpi-row {
  margin-bottom: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--ta-r-xl);
  /* Same reason as section.gauge: a clipping pane in a flex column must not be
     shrinkable, or a tall page crops its contents instead of scrolling. */
  flex-shrink: 0;
}

.kpi-row-head {
  padding: 0.875rem 1.25rem;
  border-bottom: 1px solid var(--ta-border);
}

.kpi-row-heading {
  margin: 0;
  font-size: 1.0625rem;
  line-height: 1.35;
  font-weight: 500;
  letter-spacing: -0.014em;
  border-bottom: none;
  padding-bottom: 0;
}

.kpi-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  min-height: 0;
  flex: 1;
  background: transparent;
  border: none;
}

/* The rules between tiles: horizontal when stacked, vertical once the row is
   side by side (the reference's `divide-y sm:divide-x sm:divide-y-0`). */
.kpi-tile+.kpi-tile {
  border-top: 1px solid var(--ta-border);
}

@media (min-width: 640px) {
  .kpi-row[data-cols="2"] .kpi-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .kpi-row[data-cols="3"] .kpi-grid {
    grid-template-columns: repeat(3, 1fr);
  }

  .kpi-row[data-cols="4"] .kpi-grid {
    grid-template-columns: repeat(4, 1fr);
  }

  .kpi-row:not([data-cols="1"]) .kpi-tile+.kpi-tile {
    border-top: none;
    border-left: 1px solid var(--ta-border);
  }
}

.kpi-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 1.25rem;
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
}

.kpi-label {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Money-Is-Mono: a KPI headline is a numeral first. */
.kpi-value {
  margin: 0.375rem 0 0;
  font-family: var(--ta-mono);
  font-size: 1.875rem;
  line-height: 2.25rem;
  font-weight: 600;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
}

/* A lone tile is the whole panel's point — it gets the hero step, and its label
   is dropped by the component because the header already named it. */
.kpi-value.single {
  font-size: 2.25rem;
}

.kpi-tile:not(:has(> .kpi-label)) .kpi-value {
  margin-top: 0;
}

.kpi-caption {
  margin: 0.25rem 0 0;
  font-size: 0.75rem;
  line-height: 1.3;
  color: var(--ta-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.kpi-icon {
  position: absolute;
  top: 1.125rem;
  right: 1.125rem;
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  border-radius: var(--ta-r);
  background: var(--ta-accent-soft);
  color: var(--ta-accent-ink, var(--ta-accent));
}

.kpi-icon svg {
  width: 1.125rem;
  height: 1.125rem;
}

/* ---- Chart cards (in-app density) ----------------------------------------- */

.chart-card {
  padding: 1.375rem 1.5rem 1.5rem;
  box-shadow: var(--ta-shadow-sm);
}

.chart-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 1.125rem;
  color: var(--ta-muted);
  font-size: 0.75rem;
}

.chart-title::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: var(--ta-accent);
  flex: none;
}

.chart-canvas-wrap {
  height: 15rem;
  margin-bottom: 1.25rem;
}

/* ---- Table cells: rich values, chips, confidence, relative time ----------- */

/* The generic data table (src/components/root/table.rs) renders as one
   rounded, hairline-bordered surface with a frosted toolbar and generous
   rows — Apple list-view treatment. Everything below is scoped under
   `.table-surface` so standalone uses of `.table-scroll` (admin, ops)
   keep their own chrome; the toolbar/header/frame grammar lives with the
   `.table-surface` block at the end of this file. */

/* Title cell — the row's identity: medium weight, accent on hover with a
   soft offset underline (Apple's list-link). */
.table-surface tbody td .table-title {
  color: var(--ta-text);
  font-weight: 600;
  text-decoration: none;
}

.table-surface tbody td .table-title:hover {
  color: var(--ta-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in oklab, var(--ta-accent) 55%, transparent);
}

/* Text values read quieter than titles. A value sizes itself to its content
   (no arbitrary cap) so identity strings like an alarm inbox alias always
   show in full when there's room — the table's horizontal scroll band is the
   "no room" case. Ellipsis stays as a safety net for genuinely
   width-constrained cells, and the phone layout caps it at 8rem (no space
   on a handset). */
.table-surface .cell-text {
  color: var(--ta-muted);
  display: inline-block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

/* Select values become tone chips (dot + label). The soft/ink triple
   (feedback.css) keeps the label legible on the tinted fill — using the raw
   saturated base here is exactly what tone-ink was introduced to fix. */
.table-surface .state-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.2rem 0.625rem;
  border: 1px solid color-mix(in oklab, var(--tone, var(--ta-border-strong)) 45%, transparent);
  border-radius: 999px;
  background: var(--tone-soft, var(--ta-surface));
  color: var(--tone-ink, var(--ta-text));
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
  text-transform: capitalize;
}

.table-surface .state-chip::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: var(--tone, var(--ta-faint));
  flex: none;
}

/* A chip with a leading value icon (e.g. placement: cpu = edge, cloud =
   cloud) swaps the status dot for the glyph — the icon IS the status mark,
   sized to sit on the chip's cap-height and tinted by the same tone var. */
.table-surface .state-chip.has-icon::before {
  display: none;
}

.table-surface .state-chip .chip-icon {
  width: 0.8125rem;
  height: 0.8125rem;
  flex: none;
  color: var(--tone, var(--ta-faint));
}

/* Numbers: mono + tabular (right-aligned via .cell-align-right). */
.table-surface .cell-num {
  font-family: var(--ta-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.8125rem;
  color: var(--ta-text);
  white-space: nowrap;
}

/* Percentage bar: a thin track + % value, tinted by `--tone`. Shared by
   detection confidence (the `.tone-err` / `.tone-warn` class on the fill sets
   `--tone` by severity) and the device `load`/`storage` columns (tinted by
   the value at the gauge's 70%/90% thresholds), so a hot/critical cell reads
   red before the label does. */
.table-surface .conf-cell {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
  min-width: 6.5rem;
}

.table-surface .conf-bar {
  flex: none;
  width: 3.25rem;
  height: 0.3125rem;
  border-radius: 999px;
  background: var(--ta-surface-2);
  overflow: hidden;
}

.table-surface .conf-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg,
      color-mix(in oklab, var(--tone, var(--ta-accent)) 32%, transparent),
      var(--tone, var(--ta-accent)));
}

.table-surface .conf-value {
  font-family: var(--ta-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--ta-muted);
  min-width: 2.5rem;
  text-align: right;
}

/* Absolute timestamps: clock time primary, date muted beneath. */
.table-surface .cell-time {
  display: inline-flex;
  flex-direction: column;
  gap: 0.1rem;
  color: var(--ta-text);
  white-space: nowrap;
  font-size: 0.88rem;
}

.table-surface .cell-time .cell-abs {
  color: var(--ta-faint);
  font-family: var(--ta-mono);
  font-size: 0.6875rem;
}

/* Media thumbs: a still at the row's own height, in the media's own shape.
   Detection clips are square (the model's 512px input), photos are 16:9 or
   4:3 — a fixed box would crop the evidence out of the frame, so the tile
   takes its width from the media and nothing is cut. Clicking opens it full
   size in the lightbox (static/app/table-media.js). */
.table-surface .media-thumb {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  /* The role="button" tile inherits control padding otherwise, which insets
     the frame and reads as a stray white margin around the media. */
  padding: 0;
  gap: 0;
  border: 1px solid var(--ta-border);
  border-radius: var(--ta-r-sm);
  background: var(--ta-surface-2);
  overflow: hidden;
  cursor: pointer;
  transition:
    border-color 140ms ease-out,
    box-shadow 140ms ease-out,
    transform 140ms ease-out;
}

.table-surface .media-thumb:hover,
.table-surface .media-thumb:focus-visible {
  border-color: color-mix(in oklab, var(--ta-accent) 55%, transparent);
  box-shadow: 0 4px 12px -4px rgba(0, 0, 0, 0.28);
  transform: translateY(-1px);
}

/* The media carries the size and the tile shrink-wraps it — a fixed height,
   width from the frame's own shape. (A percentage height here would be
   cyclic against a shrink-to-fit tile and collapse.) `contain` matters in a
   narrow column: when the tile is clamped by the column the frame stays
   whole instead of stretching. */
.table-surface .media-thumb .media-thumb-still {
  display: block;
  width: auto;
  max-width: 100%;
  height: 5rem;
  object-fit: contain;
}

/* The play affordance is permanent, not a hover reveal: in a scan of a
   hundred rows the tile has to read as "clip" at a glance, and on touch
   there is no hover to reveal it. A corner badge over a short scrim keeps
   the frame itself unobscured. */
.table-surface .media-thumb-play {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  align-items: flex-end;
  padding: 0.3rem;
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.55));
  color: rgba(255, 255, 255, 0.92);
  transition: color 140ms ease-out;
}

.table-surface .media-thumb-play svg {
  width: 0.85rem;
  height: 0.85rem;
  fill: currentColor;
  stroke: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
  transition: transform 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.table-surface .media-thumb:hover .media-thumb-play,
.table-surface .media-thumb:focus-visible .media-thumb-play {
  color: #fff;
}

.table-surface .media-thumb:hover .media-thumb-play svg {
  transform: scale(1.18);
}

@media (prefers-reduced-motion: reduce) {

  .table-surface .media-thumb,
  .table-surface .media-thumb-play svg {
    transition: none;
  }

  .table-surface .media-thumb:hover,
  .table-surface .media-thumb:focus-visible {
    transform: none;
  }

  .table-surface .media-thumb:hover .media-thumb-play svg {
    transform: none;
  }
}

/* A file the browser can't preview keeps its "view" chip shape — the extra
   class out-specifies the row-height sizing in modern.css. */
.table-surface .media-thumb.media-thumb-other {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  height: 2rem;
  padding: 0 0.75rem;
  font-size: 0.78rem;
  color: var(--ta-muted);
  text-decoration: none;
}

/* ── Media lightbox ────────────────────────────────────────────────────────
   Where a table's media is actually watched: a native <dialog>, so Esc, the
   focus trap and the inert page come from the platform rather than from JS.
   The stage is dark and sized to the media — a 512px detection clip plays at
   512px, an 8MP photo caps at the viewport — and the bar underneath names
   the file and hands over the original. Built by static/app/table-media.js. */
.media-lightbox {
  width: max-content;
  max-width: min(94vw, 68rem);
  max-height: 92vh;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ta-text);
  overflow: visible;
}

.media-lightbox::backdrop {
  background: rgba(9, 11, 16, 0.72);
  backdrop-filter: blur(3px);
}

.media-lightbox-frame {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--ta-border);
  border-radius: var(--ta-r-lg);
  background: var(--ta-surface);
  box-shadow: 0 24px 64px -16px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}

/* Deep neutral rather than pure black: annotation boxes are drawn in
   saturated colour and read better off a surface that isn't a void. */
.media-lightbox-stage {
  display: grid;
  place-items: center;
  min-width: 12rem;
  min-height: 8rem;
  background: #0d0f14;
}

.media-lightbox-stage video,
.media-lightbox-stage img {
  display: block;
  width: auto;
  height: auto;
  max-width: min(94vw, 68rem);
  max-height: 74vh;
}

.media-lightbox-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0.5rem 0.5rem 0.75rem;
  border-top: 1px solid var(--ta-border);
  background: var(--ta-surface);
}

.media-lightbox-name {
  min-width: 0;
  overflow: hidden;
  color: var(--ta-muted);
  font-family: var(--ta-mono);
  font-size: 0.7rem;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.media-lightbox-actions {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 0.15rem;
}

.media-lightbox-actions a,
.media-lightbox-actions button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--ta-r-sm);
  background: none;
  color: var(--ta-muted);
  cursor: pointer;
  transition:
    background-color 120ms ease-out,
    color 120ms ease-out;
}

.media-lightbox-actions a:hover,
.media-lightbox-actions button:hover,
.media-lightbox-actions a:focus-visible,
.media-lightbox-actions button:focus-visible {
  background: var(--ta-surface-2);
  color: var(--ta-text);
}

.media-lightbox-actions svg {
  width: 1rem;
  height: 1rem;
}

.media-lightbox-sep {
  width: 1px;
  height: 1.1rem;
  margin: 0 0.25rem;
  background: var(--ta-border);
}

/* The one authored moment: the frame settles up out of the backdrop. */
.media-lightbox[open] .media-lightbox-frame {
  animation: media-lightbox-in 190ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

@keyframes media-lightbox-in {
  from {
    opacity: 0;
    transform: scale(0.985) translateY(6px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .media-lightbox[open] .media-lightbox-frame {
    animation: none;
  }

  .media-lightbox::backdrop {
    backdrop-filter: none;
  }
}

/* Empty media: a quiet "No clip / No photo" placeholder. */
.table-surface .table-media-empty {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--ta-faint);
  font-size: 0.75rem;
  white-space: nowrap;
}

.table-surface .table-media-empty svg {
  width: 0.9rem;
  height: 0.9rem;
  opacity: 0.8;
}

/* ── Empty states ── */
.table-surface .table-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  padding: 3.25rem 1rem;
  text-align: center;
}

.table-surface .table-empty svg {
  width: 2rem;
  height: 2rem;
  color: var(--ta-faint);
  opacity: 0.7;
  margin-bottom: 0.25rem;
}

.table-surface .table-empty p {
  margin: 0;
  color: var(--ta-muted);
  font-size: 0.9375rem;
  font-weight: 550;
}

.table-surface .table-empty .table-empty-hint {
  color: var(--ta-faint);
  font-size: 0.8125rem;
  font-weight: 400;
}

.table-surface .table-empty-action {
  margin-top: 0.75rem;
  padding: 0.35rem 0.9rem;
  border: 1px solid var(--ta-border-strong);
  border-radius: var(--ta-r);
  background: var(--ta-surface);
  color: var(--ta-accent);
  font-size: 0.82rem;
  font-weight: 600;
  text-decoration: none;
  transition: border-color 130ms ease-out, background-color 130ms ease-out;
}

.table-surface .table-empty-action:hover {
  border-color: var(--ta-accent);
  background: var(--ta-accent-soft);
}

/* ---- Mobile: collapse the dashboard grid ---------------------------------- */

@media (max-width: 1024px) {
  main.app-main:has(> section.kpi-row) {
    display: block;
  }

  main.app-main:has(> section.kpi-row)>* {
    margin-block: 0 1.25rem;
  }
}

/* ===========================================================================
   The generic data table's surface grammar (.table-surface)
   ---------------------------------------------------------------------------
   One rounded, hairline-bordered card: a frosted toolbar (search + count +
   create), a quiet regular-case header, generously padded rows with hairline
   separators, and a pagination footer. One deliberate departure from the
   platform's uppercase micro-labels: table headers read in regular case,
   medium weight — the Apple list-view convention — so the table settles
   instead of shouting. Scoped under `.table-surface` so standalone uses of
   `.table-scroll` (admin, ops) keep their own frame.
   ========================================================================== */

.table-surface {
  border: 1px solid var(--ta-border);
  border-radius: var(--ta-r-xl);
  background: var(--ta-surface);
  box-shadow: var(--ta-shadow-sm);
  overflow: hidden;
  /* rounds the scroll band + footer with the card */
}

/* A `heading:` override names a distinct view (comparematerials etc.) — a
   quiet title above the surface, not another box. */
.table-heading {
  margin: 0 0 1rem;
  padding: 0;
  border: 0;
  font-size: 1.05rem;
  font-weight: 650;
  color: var(--ta-text);
}

/* ── Toolbar: search, count, create ── */
.table-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.75rem 0.875rem;
  border-bottom: 1px solid var(--ta-border);
  background: color-mix(in oklab, var(--ta-surface-2) 55%, transparent);
}

.table-toolbar-side {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-left: auto;
}

.table-count {
  color: var(--ta-muted);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The search field — a control, not a chrome box: rounded, hairline, soft
   focus ring, with the magnifier, a clear button and a "/" key hint. A
   solid `--ta-surface` fill (not a `color-mix` with transparent, which can
   lag the live theme toggle) reads as a defined field on the tinted
   toolbar. */
.table-search {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1 1 14rem;
  max-width: 26rem;
  height: 2.5rem;
  padding: 0 0.625rem 0 0.75rem;
  border: 1px solid var(--ta-border-strong);
  border-radius: var(--ta-r-lg);
  background: var(--ta-surface);
  box-shadow: inset 0 1px 0 var(--ta-border);
  transition: border-color 140ms ease-out, box-shadow 140ms ease-out,
    background-color 140ms ease-out;
}

.table-search:focus-within {
  border-color: color-mix(in oklab, var(--ta-accent) 55%, transparent);
  box-shadow: 0 0 0 3px var(--ta-accent-soft);
  background: var(--ta-surface);
}

.table-search-glyph {
  display: flex;
  flex: none;
  color: var(--ta-faint);
}

.table-search-glyph svg {
  width: 1rem;
  height: 1rem;
}

.table-search-input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ta-text);
  font: inherit;
  font-size: 0.9rem;
  appearance: none;
  -webkit-appearance: none;
}

.table-search-input:focus {
  outline: none;
  border: 0;
  box-shadow: none;
}

.table-search-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.table-search-input::placeholder {
  color: var(--ta-faint);
}

.table-search-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.375rem;
  height: 1.375rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--ta-surface-2);
  color: var(--ta-muted);
  cursor: pointer;
  transition: background-color 120ms ease-out, color 120ms ease-out;
}

/* Author `display: inline-flex` above would otherwise override the UA's
   `[hidden] { display: none }` — keep the clear button hideable. */
.table-search-clear[hidden] {
  display: none;
}

.table-search-clear:hover {
  background: var(--ta-border-strong);
  color: var(--ta-text);
}

.table-search-clear svg {
  width: 0.75rem;
  height: 0.75rem;
}

.table-search-kbd {
  flex: none;
  padding: 0.125rem 0.4rem;
  border: 1px solid var(--ta-border-strong);
  border-bottom-width: 2px;
  border-radius: var(--ta-r-xs);
  background: var(--ta-surface-2);
  color: var(--ta-faint);
  font-family: var(--ta-mono);
  font-size: 0.625rem;
  line-height: 1.2;
}

@media (hover: none) {
  .table-search-kbd {
    display: none;
  }
}

/* The scroll band drops the standalone frame inside a surface. */
.table-surface .table-scroll {
  border: none;
  border-radius: 0;
  background: transparent;
}

/* ── Header: quiet, regular case, sortable ── */
.table-surface thead th {
  padding: 0.8rem 1.125rem;
  border-bottom: 1px solid var(--ta-border);
  background: transparent;
  color: var(--ta-muted);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-transform: none;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  text-align: left;
}

.table-surface thead th a {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: inherit;
  text-decoration: none;
  transition: color 120ms ease-out;
}

.table-surface thead th a:hover {
  color: var(--ta-text);
}

.table-surface thead th a.sorted {
  color: var(--ta-accent);
  font-weight: 650;
}

.table-surface thead th a .sort-arrow {
  font-size: 0.7em;
  opacity: 0.65;
}

/* ── Rows: generous padding, hairline separators, soft hover ── */
.table-surface tbody td {
  padding: 0.85rem 1.125rem;
  border-bottom: 1px solid var(--ta-border);
  font-size: 0.9rem;
  color: var(--ta-muted);
  vertical-align: middle;
}

.table-surface tbody tr:last-child td {
  border-bottom: none;
}

.table-surface tbody tr {
  transition: background-color 130ms ease-out;
}

.table-surface tbody tr:hover,
.table-surface tbody tr:focus-within {
  background: var(--ta-surface-2);
}

/* Whole row is a link — `tr[data-href]` (wired by app/table-row-click.js)
   navigates to the record on click, except over the row's own interactive
   controls. Pointer cursor over the row signals it; each cell's native
   control (a link, a button, an input's text caret) keeps its own cursor
   because `cursor` on the row is only inherited where a control doesn't
   set one of its own. */
.table-surface tbody tr[data-href] {
  cursor: pointer;
}

.table-surface .cell-align-right {
  text-align: right;
}

/* ── Events table: dense scan-height rows ──────────────────────────────────
   The events feed is the busiest table in the app (hundreds of rows, a
   thumbnail in every one), so it compacts to a scan height: slimmer header
   and row padding plus a smaller thumbnail tile — roughly half the row
   height of the standard list-view table. Scoped under the block's
   `data-entity="events"` (rendered by src/components/root/table.rs) so the
   other tables — sites, cameras, devices — keep their generous Apple
   list-view rows. */
section[data-entity="events"] .table-surface thead th {
  padding: 0.5rem 1rem;
}

section[data-entity="events"] .table-surface tbody td {
  padding: 0.375rem 1rem;
}

section[data-entity="events"] .table-surface .media-thumb-still {
  height: 2.75rem;
}

/* The chip, confidence bar and two-line time scale down with the row so the
   denser rows still read as one cadence instead of scattered sizes. */
section[data-entity="events"] .table-surface .state-chip {
  padding: 0.125rem 0.5rem;
  font-size: 0.68rem;
}

section[data-entity="events"] .table-surface .conf-bar {
  height: 0.25rem;
}

section[data-entity="events"] .table-surface .cell-time {
  gap: 0;
  font-size: 0.82rem;
}

section[data-entity="events"] .table-surface .cell-time .cell-abs {
  font-size: 0.625rem;
}

/* ── Footer / pagination: text-only page controls, Apple-style ── */
.table-footer {
  display: flex;
  justify-content: flex-end;
  padding: 0.5rem 0.875rem;
  border-top: 1px solid var(--ta-border);
  background: var(--ta-surface-2);
}

.table-footer .pagination {
  margin: 0;
  gap: 0.375rem;
}

.table-footer .pagination a {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.5rem;
  border: 0;
  border-radius: var(--ta-r-xs);
  background: transparent;
  color: var(--ta-accent);
  font-size: 0.8rem;
  font-weight: 600;
  text-decoration: none;
  transition: background-color 130ms ease-out;
}

.table-footer .pagination a:hover {
  border: 0;
  background: var(--ta-accent-soft);
  color: var(--ta-accent);
}

.table-footer .pagination span {
  font-size: 0.8rem;
}

/* ── Narrow screens: search takes its own line, count + create split ── */
@media (max-width: 640px) {
  .table-toolbar {
    padding: 0.625rem;
  }

  .table-search {
    flex-basis: 100%;
    max-width: none;
  }

  .table-toolbar-side {
    margin-left: 0;
    width: 100%;
    justify-content: space-between;
  }
}