/* ==========================================================================
   PAGES — shared UI pattern across Servers, Events, Incidents, Sites, Users,
   Audit, Storage and the new pages we ported to the Cameras / Settings style.
   ========================================================================== */

/* Page shell: a single max-width column with a tall header and stacked cards.
   W4-b/U7: gap was 18 (off-scale) → --page-shell-gap (20). +2px shift. */
.page-shell { display: flex; flex-direction: column; gap: var(--page-shell-gap); min-width: 0; }

/* W4-b/U7: replaces the CameraDetail `style="padding-top:0"` one-off. The
   error-banner-state on CameraDetail.js renders the alert flush with the
   appbar bottom border (no breathing room top), but otherwise keeps the
   canonical .content shell. */
.content.content--flush-top { padding-top: 0; }

/* W4-b/U7: vertical-stack utilities for between-section rhythm.
   Use on a flex/grid parent to set the gap to a canonical token value.
   Replaces the margin-between-siblings anti-pattern flagged by the audit.
   .stack-N uses --space-N (so .stack-2 = 8px gap, .stack-6 = 24px gap, etc).
   For between-section rhythm prefer .stack-6 (matches --section-gap). */
.stack-2 { display: flex; flex-direction: column; gap: var(--space-2); }
.stack-3 { display: flex; flex-direction: column; gap: var(--space-3); }
.stack-4 { display: flex; flex-direction: column; gap: var(--space-4); }
.stack-5 { display: flex; flex-direction: column; gap: var(--space-5); }
.stack-6 { display: flex; flex-direction: column; gap: var(--space-6); }

.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  /* W4-b/U7: was 18 (off-scale) → --space-5 (20). +2px shift. */
  gap: var(--space-5);
  padding: var(--space-1) var(--space-1) 0;
}
/* Page H1 — canonical visible page title. W3-c: lifted to --fs-3xl, but
   kept at 24px (--fs-2xl) here so existing layouts shift ≤2px per the brief.
   The audit's 13-px breadcrumb-as-page-title regression is resolved by the
   .page-head h1 already being visible above the content. */
.page-head h1 {
  font-size: var(--fs-2xl); font-weight: var(--fw-700);
  color: var(--fg-1); margin: 0 0 4px; letter-spacing: -.01em;
  line-height: var(--lh-tight);
  font-family: var(--font-sans);
}
.page-head p  {
  font-size: var(--fs-base); color: var(--fg-3); margin: 0;
  max-width: 620px; line-height: var(--lh-relaxed);
  font-family: var(--font-sans);
}
.page-actions { display: flex; gap: var(--space-2); align-items: center; }

.page-head.with-back > div { display: flex; flex-direction: column; gap: 6px; }
.page-head .focus-back {
  display: inline-flex; align-items: center; gap: 6px;
  /* W-E/F-BTNH: was raw 30px (off the canonical scale). Compact back-pill
     → --control-h-sm (28px), the small control height shared with chips. */
  height: var(--control-h-sm, 28px); padding: 0 10px;
  background: var(--bg-surface); color: var(--fg-3);
  border: 1px solid var(--border-1);
  border-radius: 7px;
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-600);
  cursor: pointer; width: max-content;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.page-head .focus-back:hover { background: var(--dnuv-blue-50); color: var(--dnuv-blue-dark); border-color: var(--dnuv-blue-200); }
.page-head .focus-back i { width: 13px; height: 13px; }
.page-breadcrumb { display: inline-flex; align-items: center; gap: var(--space-2); }

/* Page tabs (Servers / Users / Storage / Events sub-sections) */
.page-tabs {
  display: inline-flex; gap: var(--space-1);
  padding: var(--space-1);
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: 10px;
  box-shadow: 0 1px 2px rgba(10,31,94,.04);
  align-self: flex-start;
}
.page-tab {
  display: inline-flex; align-items: center; gap: 7px;
  /* W-E/F-BTNH: was raw 32px. Page sub-section tabs sit beside .btn in the
     page-head toolbar → align to the canonical --control-h (36px) so the row
     has one control height (no 36-vs-32 drift). */
  height: var(--control-h, 36px); padding: 0 12px;
  background: transparent; border: none;
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-600);
  color: var(--fg-3);
  cursor: pointer; border-radius: 7px;
  transition: background 120ms, color 120ms;
}
.page-tab i { width: 14px; height: 14px; color: var(--fg-4); transition: color 120ms; }
.page-tab:hover { background: var(--bg-sunken); color: var(--fg-1); }
.page-tab:hover i { color: var(--fg-2); }
.page-tab.active { background: var(--dnuv-blue-50); color: var(--dnuv-blue-dark); }
.page-tab.active i { color: var(--dnuv-blue-dark); }
.page-tab .tab-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px;
  background: rgba(10,31,94,.08); color: var(--fg-3);
  border-radius: 9px; font-size: var(--fs-xs); font-weight: var(--fw-700); letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}
.page-tab.active .tab-count { background: var(--dnuv-blue-dark); color: #fff; }
.page-tab .tab-count.danger { background: var(--dnuv-danger); color: #fff; }

/* Shared row-meta utilities */
.row-meta    { font-size: var(--fs-sm); color: var(--fg-3); font-variant-numeric: tabular-nums; }
.row-meta-sm { font-size: var(--fs-xs);   color: var(--fg-4); margin-top: 2px; }

/* Filter chips — bumped count chip */
.filter-chip .filter-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 6px;
  margin-left: 4px;
  background: rgba(10,31,94,.06);
  color: var(--fg-3);
  border-radius: 9px;
  font-size: var(--fs-xs); font-weight: var(--fw-800);
  font-variant-numeric: tabular-nums;
}
.filter-chip.active .filter-count { background: rgba(0, 34, 140, 0.15); color: var(--dnuv-blue-dark); }

/* Server icons (small) */
.srv-ico {
  width: 32px; height: 32px; flex: 0 0 32px;
  border-radius: 8px;
  display: grid; place-items: center;
}
/* R267 (W4-a/U4-P0-4) — Status-pill drift removed; canonical tokens. */
.srv-ico.ok  { background: var(--dnuv-success-bg); color: var(--color-success); }
.srv-ico.off { background: var(--dnuv-danger-bg);  color: var(--color-danger); }
.srv-ico i { width: 16px; height: 16px; }

/* KPI pills for Servers Health */
.srv-kpi-strip { display: inline-flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; flex: 1; min-width: 0; }
.kpi-pill {
  display: inline-flex; align-items: center; gap: 6px;
  height: 28px; padding: 0 10px;
  border-radius: 999px;
  font-size: var(--fs-sm); font-weight: var(--fw-600);
  background: var(--bg-sunken);
  color: var(--fg-2);
}
.kpi-pill .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--fg-4); }
/* R267 (W4-a/U4-P0-4) — Status-pill drift removed; canonical tokens. */
.kpi-pill.ok   { background: var(--dnuv-success-bg); color: var(--color-success); }
.kpi-pill.ok   .dot { background: var(--color-success); }
.kpi-pill.warn { background: var(--dnuv-warning-bg); color: var(--color-warn); }
.kpi-pill.warn .dot { background: var(--color-warn); }
.kpi-pill.off  { background: var(--dnuv-danger-bg); color: var(--color-danger); }
.kpi-pill.off  .dot { background: var(--dnuv-danger); }

/* Health list */
.srv-health-list { display: flex; flex-direction: column; }
.srv-health-row {
  display: grid;
  grid-template-columns: 12px 1fr auto;
  gap: 14px; align-items: center;
  padding: 14px 18px;
  border-bottom: 1px solid rgba(10,31,94,.05);
}
.srv-health-row:last-child { border-bottom: none; }
.srv-health-row .led {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--fg-4);
}
.srv-health-row .led.ok   { background: var(--dnuv-success); box-shadow: 0 0 0 3px rgba(15,186,129,.18); }
.srv-health-row .led.warn { background: var(--dnuv-warning);            box-shadow: 0 0 0 3px rgba(245,158,11,.18); }
.srv-health-row .led.fail { background: var(--dnuv-danger);  box-shadow: 0 0 0 3px rgba(229,72,77,.18); }
.srv-health-text b { display: block; font-size: var(--fs-small); color: var(--fg-1); }
.srv-health-text span { font-size: var(--fs-sm); color: var(--fg-4); }

/* Status pill variants for Health / Events / Users */
.pill-ok,
.pill-warn,
.pill-off {
  display: inline-flex; align-items: center; justify-content: center;
  height: 22px; padding: 0 10px; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: var(--fw-700); letter-spacing: .04em;
}
/* R267 (W4-a/U4-P0-4) — Status-pill drift removed; canonical tokens. */
.pill-ok   { background: var(--dnuv-success-bg); color: var(--color-success); }
.pill-warn { background: var(--dnuv-warning-bg); color: var(--color-warn); }
.pill-off  { background: var(--dnuv-danger-bg);  color: var(--color-danger); }

/* W4-c / U4 P1 — `.activity-row` canonical base shared by Events page rows,
 * Overview "Atividade últimas 24h" rows, and Audit log rows. Each consumer
 * keeps its own grid-template-columns (different cell counts) but inherits
 * the common border, transition, hover, and last-child trim from this rule.
 * Before consolidation the same 6 declarations were repeated across three
 * selectors with 3 different gap/padding combinations. */
.activity-row,
.event-row,
.audit-row,
.ov-activity-list .event-row {
  display: grid;
  gap: var(--space-3); align-items: center;
  /* W4-b/U7: was 12×18 → --table-row-py × --table-row-px (12×16). -2 horiz. */
  padding: var(--table-row-py) var(--table-row-px);
  border-bottom: 1px solid rgba(10,31,94,.05);
  transition: background 100ms;
}
.activity-row:last-child,
.event-row:last-child,
.audit-row:last-child,
.ov-activity-list .event-row:last-child { border-bottom: none; }
.activity-row:hover,
.event-row:hover,
.audit-row:hover { background: var(--bg-sunken); }

/* Events list (Events page) */
/* W4-d/U13 P0-2 (Option B): Events is data-dense — timestamp matters even
   on phone. The list scrolls horizontally inside `.events-list` so the row
   keeps all 4 columns visible (instead of collapsing). The min-width below
   guarantees the row never compresses smaller than its content. */
.events-list {
  display: flex; flex-direction: column;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
}
/* R397 v2 (Eventos table) — the row markup (Events.js) makes the DIRECT
   children [checkbox][.event-open][.event-ack?][.event-menu] — NOT the flat
   [time][ico][text][menu] the old 4-track grid assumed. R390 restructured the
   row (time/ico/text now nest inside the .event-open button) but never updated
   this CSS, so the grid mapped checkbox→64px, the whole .event-open→28px (it
   wrapped char-by-char), etc. Switch the row to flex and let .event-open be the
   inner flex container that holds time/icon/text. This flex overrides the shared
   `display:grid` from the `.activity-row,.event-row,…` base rule above (equal
   specificity, later source order). */
.event-row {
  display: flex; align-items: center; gap: var(--space-3);
  /* W4-b/U7: was 12×18 → --table-row-py × --table-row-px (12×16). -2 horiz. */
  padding: var(--table-row-py) var(--table-row-px);
  /* W4-d/U13 P0-2: keep timestamp + icon + text + menu legible on phone
     (parent .events-list scrolls horizontally past this min-width). */
  min-width: 480px;
}
/* The clickable "open details" cell: flexes to fill the row and lays out
   time · icon · text inline. Reset the bare-button UA chrome. */
.event-open {
  appearance: none; -webkit-appearance: none;
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: var(--space-3);
  background: none; border: 0; padding: 0; margin: 0;
  font-family: inherit; text-align: left; cursor: pointer; color: inherit;
}
.event-open:focus-visible { outline: none; box-shadow: var(--ring-focus); border-radius: 6px; }
/* Leading select checkbox — fixed cell so it never stretches. */
.event-check { flex: 0 0 auto; margin: 0; }
/* Reconhecer (ack) — alert-only square button cell. */
.event-ack {
  flex: 0 0 auto;
  width: 32px; height: 32px;
  background: transparent; border: none;
  color: var(--fg-4); border-radius: 7px;
  display: grid; place-items: center; cursor: pointer;
}
.event-ack:hover { background: var(--bg-sunken); color: var(--fg-1); }
.event-ack i { width: 14px; height: 14px; }
.event-row.is-alert { background: rgba(229,72,77,0.04); }
.event-row.is-alert:hover { background: rgba(229,72,77,0.07); }
.event-time { flex: 0 0 auto; font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--fg-4); white-space: nowrap; }
.event-ico {
  width: 28px; height: 28px; border-radius: 7px;
  display: grid; place-items: center; flex: 0 0 28px;
}
.event-ico i { width: 14px; height: 14px; }
.event-ico.tone-info   { background: var(--dnuv-blue-50);     color: var(--dnuv-blue-dark); }
/* W-E/F-DARK: was rgba(255,176,32,.16) bg + #5C3700 text (light-only warn);
   the dark-brown text on the amber wash fails AA in dark mode. Canonical
   --color-warn-bg/-fg flip to a dark translucent wash + light amber text. */
.event-ico.tone-warn   { background: var(--color-warn-bg);    color: var(--color-warn-fg); }
.event-ico.tone-danger { background: var(--dnuv-danger-bg);   color: var(--dnuv-danger); }
.event-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.event-h    { font-size: var(--fs-base); color: var(--fg-1); font-weight: var(--fw-500); }
.event-h b  { font-weight: var(--fw-700); color: var(--fg-1); }
.event-meta { font-size: var(--fs-sm); color: var(--fg-4); }
.event-alert-pill {
  display: inline-flex; align-items: center;
  height: 18px; padding: 0 8px; border-radius: 999px;
  background: var(--dnuv-danger); color: #fff;
  font-size: var(--fs-xs); font-weight: var(--fw-800); text-transform: uppercase; letter-spacing: .05em;
}
.event-menu {
  flex: 0 0 auto;
  width: 32px; height: 32px;
  background: transparent; border: none;
  color: var(--fg-4); border-radius: 7px;
  display: grid; place-items: center; cursor: pointer;
}
.event-menu:hover { background: var(--bg-sunken); color: var(--fg-1); }
.event-menu i { width: 14px; height: 14px; }

/* Incidents — severity + status pills */
.sev-pill {
  display: inline-flex; align-items: center;
  height: 22px; padding: 0 10px; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: var(--fw-700); letter-spacing: .03em;
}
/* R390 (C0 followup) — `critical` is the single highest-urgency severity. C10
   added the alert-octagon glyph (so it isn't colour-only); this gives it the
   filled danger tone (solid red + white, mirroring `.event-alert-pill`) so it
   reads as MORE urgent than the tinted `.high` wash. White on --dnuv-danger
   (#E5484D, unchanged in dark mode) clears AA in both themes. */
.sev-pill.critical { background: var(--dnuv-danger); color: #fff; gap: 5px; }
.sev-pill.critical i { width: 12px; height: 12px; }
.sev-pill.high   { background: var(--dnuv-danger-bg);   color: var(--dnuv-danger); }
/* W-E/F-DARK: was rgba(255,176,32,.18) bg + #5C3700 text — dark-mode-unsafe.
   Canonical warn tokens flip to a dark wash + light amber text. */
.sev-pill.medium { background: var(--color-warn-bg);    color: var(--color-warn-fg); }
.sev-pill.low    { background: var(--dnuv-blue-50);     color: var(--dnuv-blue-dark); }

.status-pill {
  display: inline-flex; align-items: center; gap: 6px;
  height: 22px; padding: 0 10px; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: var(--fw-700);
  background: var(--bg-sunken); color: var(--fg-2);
}
/* R267 (W4-a/U4-P0-4) — Status-pill drift removed; canonical tokens. */
.status-pill.open          { background: var(--dnuv-danger-bg);  color: var(--color-danger); }
.status-pill.investigating { background: var(--dnuv-warning-bg); color: var(--color-warn); }
.status-pill.resolved      { background: var(--dnuv-success-bg); color: var(--color-success); }
/* R389 (C0) — archived/inactive used --fg-4 (#94A3B8) on --bg-sunken ≈ 2.36:1
   (fails AA). --fg-2 flips per theme and clears AA on the muted pill in both
   (light 9.54:1 / dark 6.97:1); the neutral grey background still reads as the
   "inactive" tone. */
.status-pill.archived      { background: var(--bg-sunken);       color: var(--fg-2); }
/* R390 (Wave-1 followup) — `.active`/`.pending` shipped the BRIGHT semantic
   tone (--color-success #0FBA81 / --color-warn #F59E0B) as the pill text. On
   the light tinted *-bg washes those read ~2.0:1 / ~1.8:1 (fail AA for the
   11px pill text). The deep `-fg` tints lift both well past 4.5:1 on light
   (#0A5C43 ≈ 6.0:1, #5C3700 ≈ 7.3:1) and flip to bright hues in dark mode
   where the *-bg becomes a dark translucent wash. */
.status-pill.active        { background: var(--dnuv-success-bg); color: var(--color-success-fg); }
.status-pill.pending       { background: var(--dnuv-warning-bg); color: var(--color-warn-fg); }
.status-pill.inactive      { background: var(--bg-sunken);       color: var(--fg-2); }

.tag-soft {
  display: inline-flex; align-items: center; gap: 5px;
  height: 20px; padding: 0 8px; border-radius: 999px;
  background: var(--bg-sunken); color: var(--fg-3);
  font-size: var(--fs-xs); font-weight: var(--fw-600);
}
.tag-soft i { width: 12px; height: 12px; }

/* R390 (Wave-1 followup) — tone variants for the soft tag/chip. The Billing
   "Modo offline" chip and the Users "Sem permissão" chip previously inlined
   `style="background:var(--*-bg);color:var(--*-fg)"`; these classes give them
   a single source. The `-fg` foregrounds clear AA on their tinted `-bg`
   (light) and flip to bright hues in dark mode (defined in colors_and_type). */
.tag-soft.danger  { background: var(--color-danger-bg);  color: var(--color-danger-fg); }
.tag-soft.warn    { background: var(--color-warn-bg);    color: var(--color-warn-fg); }
.tag-soft.success { background: var(--color-success-bg); color: var(--color-success-fg); }

/* Interactive `.tag-soft` (e.g. the Users role-picker button
   `.tag-soft.users-action-role`). When the tag is itself a <button> it needs a
   pointer cursor, a hover lift and the canonical focus ring (the global
   .btn*:focus-visible rule does not reach `.tag-soft`). The plain
   text-only tags inherit `cursor:default` from their <span>. */
.tag-soft:is(button, a, [role="button"]) {
  border: none; font-family: inherit; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.tag-soft:is(button, a, [role="button"]):hover { background: var(--bg-elev-2); color: var(--fg-1); }
.tag-soft.danger:is(button, a, [role="button"]):hover  { background: var(--color-danger-bg);  color: var(--color-danger-fg); filter: brightness(0.97); }
.tag-soft.warn:is(button, a, [role="button"]):hover     { background: var(--color-warn-bg);    color: var(--color-warn-fg);   filter: brightness(0.97); }
.tag-soft.success:is(button, a, [role="button"]):hover  { background: var(--color-success-bg); color: var(--color-success-fg); filter: brightness(0.97); }
.tag-soft:is(button, a, [role="button"]):focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
}

/* R390 (Wave-1 followup) — shared disabled style. Replaces the interim
   inline `style="opacity:.72"` the Users edit-modal read-only inputs used and
   gives every page one canonical disabled affordance. The native [disabled]
   attribute and the explicit `.is-disabled` opt-in class both qualify. */
[disabled], .is-disabled { opacity: .6; cursor: not-allowed; }

/* ===== CameraDetail — per-camera retention slider =====
   R390 (Wave-1 followup) — W14/#618 restored the 1–30 day retention slider in
   CameraDetail.js (Recording tab) but it shipped unstyled. The wrapper holds a
   <label>, a range <input data-cd-retention-slider>, an <output> echoing the
   day count, and one/two `.retention-hint` lines (one is aria-live and can
   gain `.alert-error` on a quota failure). Layout: label + range + output on
   one baseline row, hints stacked beneath. All on existing tokens. */
.retention-slider {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-1) 0 var(--space-3);
}
.retention-slider > label {
  font-size: var(--fs-sm); font-weight: var(--fw-600); color: var(--fg-2);
  white-space: nowrap;
}
.retention-slider input[type="range"] {
  width: 100%; min-width: 0; margin: 0;
  accent-color: var(--dnuv-blue-dark);
  cursor: pointer;
  background: transparent;
}
.retention-slider input[type="range"]:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  border-radius: var(--radius-pill);
}
.retention-slider input[type="range"]:disabled { cursor: not-allowed; }
.retention-slider > output {
  font-size: var(--fs-sm); font-weight: var(--fw-700); color: var(--fg-1);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  min-width: 56px; text-align: right;
}
/* The hint lines span the full width below the slider row. */
.retention-hint {
  grid-column: 1 / -1;
  margin: 0;
  font-size: var(--fs-sm); color: var(--fg-3); line-height: var(--lh-normal);
}
.retention-hint.alert-error { color: var(--color-danger-fg); font-weight: var(--fw-600); }

/* Incident detail two-column grid */
.inc-detail-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 18px; align-items: flex-start; }
.inc-detail-col { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
@media (max-width: 1000px) { .inc-detail-grid { grid-template-columns: 1fr; } }
.inc-timeline { display: flex; flex-direction: column; gap: var(--space-3); }
.inc-timeline-row { display: grid; grid-template-columns: 60px 1fr; gap: 10px; align-items: start; font-size: var(--fs-small); }
.inc-timeline-row .t { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--fg-4); padding-top: 2px; }
.inc-timeline-row .msg b { color: var(--fg-1); }
.inc-note {
  margin-top: 14px;
  display: flex; gap: var(--space-2); align-items: center;
  padding-top: 14px; border-top: 1px dashed var(--border-1);
}
.inc-note input { flex: 1; padding: 9px 12px; border: 1px solid var(--border-1); border-radius: 9px; font-family: inherit; font-size: var(--fs-small); outline: none; }
.inc-note input:focus { border-color: var(--dnuv-blue-light); box-shadow: var(--ring-focus); }
.inc-note .btn { padding: 9px 14px; }
.kv-row { display: grid; grid-template-columns: 140px 1fr; gap: 10px; padding: 8px 0; border-bottom: 1px dashed var(--border-1); }
.kv-row:last-child { border-bottom: none; }
.kv-row .k { font-size: var(--fs-sm); color: var(--fg-4); }
.kv-row .v { font-size: var(--fs-small); color: var(--fg-1); font-weight: var(--fw-500); }
.empty-mini { padding: var(--space-4); text-align: center; color: var(--fg-4); font-size: var(--fs-small); }

/* Sites map (pseudo) */
.sites-map { padding: 18px; }
.sites-map-canvas {
  position: relative;
  width: 100%; aspect-ratio: 16 / 9;
  border: 1px solid var(--border-1);
  border-radius: 12px;
  background: linear-gradient(135deg, #EDF2F8 0%, #E1EAF5 100%);
  overflow: hidden;
}
.sites-map-grid {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(10,31,94,0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(10,31,94,0.05) 1px, transparent 1px);
  background-size: 40px 40px;
}
.site-pin {
  position: absolute;
  width: 14px; height: 14px;
  transform: translate(-50%, -50%);
  cursor: pointer; z-index: 2;
}
.site-pin .pin-dot {
  position: absolute; inset: 0;
  width: 100%; height: 100%; border-radius: 50%;
  background: var(--dnuv-blue-dark);
  box-shadow: 0 0 0 4px rgba(0, 34, 140, 0.15), 0 2px 4px rgba(10,31,94,.3);
  animation: pin-pulse 2.4s infinite;
}
.site-pin.warn .pin-dot { background: var(--dnuv-warning); box-shadow: 0 0 0 4px rgba(245,158,11,0.20), 0 2px 4px rgba(245,158,11,.3); }
.site-pin .pin-label {
  display: none;
  position: absolute; left: 18px; top: 50%; transform: translateY(-50%);
  background: #061546; color: #fff;
  padding: 6px 10px; border-radius: 6px;
  font-size: var(--fs-sm); font-weight: var(--fw-600); white-space: nowrap;
  z-index: 3;
}
.site-pin .pin-label small { display: block; font-size: var(--fs-xs); font-weight: var(--fw-500); opacity: 0.7; }
.site-pin:hover .pin-label { display: block; }
@keyframes pin-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.15); }
}
.sites-map-legend {
  display: flex; align-items: center; gap: 18px;
  padding: 12px 4px 0;
  font-size: var(--fs-sm); color: var(--fg-3);
  flex-wrap: wrap;
}
.sites-map-legend .legend-item { display: inline-flex; align-items: center; gap: 6px; }
.sites-map-legend .legend-item .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--fg-4); }
.sites-map-legend .legend-item .dot.ok   { background: var(--dnuv-blue-dark); }
.sites-map-legend .legend-item .dot.warn { background: var(--dnuv-warning); }
.sites-map-legend .legend-meta { margin-left: auto; color: var(--fg-4); font-variant-numeric: tabular-nums; }

/* Users avatar (medium for the table) */
.user-avatar-md {
  width: 32px; height: 32px; flex: 0 0 32px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--dnuv-blue-dark), var(--dnuv-blue-light));
  color: #fff;
  display: grid; place-items: center;
  font-size: var(--fs-sm); font-weight: var(--fw-800);
}

/* Roles grid (Users page · Papéis tab) */
.roles-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 14px;
}
/* W4-review (cavecrew): role-card now composes `.card` in Users.js markup so
   background / border / border-radius / box-shadow inherit from the canonical
   card primitive (`.card` block declared lower in this file). Only the
   role-card-specific layout (padding rhythm + flex column + gap) and the
   role-add variant overrides live here. */
.role-card {
  padding: 16px 18px;
  display: flex; flex-direction: column; gap: var(--space-3);
}
.role-card-head { display: flex; justify-content: space-between; gap: var(--space-3); }
.role-card h3 { margin: 0; font-size: var(--fs-base); font-weight: var(--fw-700); color: var(--fg-1); }
.role-card p  { margin: 4px 0 0; font-size: var(--fs-sm); color: var(--fg-3); line-height: var(--lh-relaxed); }
.role-card-meta { display: flex; flex-direction: column; gap: var(--space-1); align-items: flex-end; flex: 0 0 auto; }
.kpi-mini { display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-xs); color: var(--fg-4); white-space: nowrap; }
.kpi-mini b { color: var(--fg-1); font-weight: var(--fw-700); font-variant-numeric: tabular-nums; }
.role-card-foot { display: flex; gap: 6px; margin-top: auto; flex-wrap: wrap; }
.role-card.role-add {
  display: flex; align-items: center; justify-content: center;
  flex-direction: column; gap: var(--space-2);
  background: var(--bg-app);
  border: 1.5px dashed var(--border-2);
  cursor: pointer; min-height: 140px;
  color: var(--fg-3);
  font-family: inherit; font-size: var(--fs-small); font-weight: var(--fw-600);
  transition: background 120ms, border-color 120ms, color 120ms;
}
.role-card.role-add:hover { background: var(--dnuv-blue-50); border-color: var(--dnuv-blue-light); color: var(--dnuv-blue-dark); }
.role-card.role-add i { width: 20px; height: 20px; }

/* Audit list — shares `.activity-row` base for border/hover/transition. */
.audit-list { padding: 0; }
.audit-row {
  /* R231 — added per-row WORM stamp cell (LGPD art. 46) between text and ip. */
  grid-template-columns: 90px 28px 140px 1fr 110px 120px;
  /* W4-b/U7: was 10×18 → --table-row-py × --table-row-px (12×16). +2/-2 shift. */
  padding: var(--table-row-py) var(--table-row-px);
  font-size: var(--fs-sm);
}
/* R396 (player-rework / A2 declutter) — the Audit list dropped the per-row WORM
   stamp column; Audit.js now tags `.audit-list` with the `.audit-no-worm`
   modifier. Drop the 110px WORM track → 5 columns (ts · ico · actor · text · ip). */
.audit-no-worm .audit-row { grid-template-columns: 90px 28px 140px 1fr 120px; }
.audit-ts { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--fg-4); }
.audit-ico {
  width: 28px; height: 28px; border-radius: 7px;
  background: var(--bg-sunken); color: var(--fg-3);
  display: grid; place-items: center;
}
.audit-ico i { width: 14px; height: 14px; }
.audit-actor { font-weight: var(--fw-600); color: var(--fg-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.audit-text { color: var(--fg-2); }
.audit-text b { color: var(--fg-1); font-weight: var(--fw-600); }
/* R396 (player-rework / A2 declutter) — `.audit-worm-cell` removed with the
   WORM column (desktop + mobile). Grep-confirmed no kit JS references it. */
.audit-ip { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--fg-4); text-align: right; }
/* W4-review (cavecrew): the legacy `@media (max-width: 800px) { .audit-row { … } }`
   block that previously sat here was deleted. It collapsed the row to
   `28px 1fr` and hid ts/actor/ip/WORM via `display:none`, which the W4-d/U13
   767px block had to clobber with 6 `!important` markers to restore the
   LGPD-required WORM cell. With the 800px block gone the 767px rules win on
   natural source order + specificity — no `!important` needed. Tablet sizes
   between 768-800px now keep the desktop layout (the only viewport where the
   800px block actually had a unique effect), which matches the W4-d intent
   ("normal layout at >=768px, card-stack <768px"). */
/* W4-d/U13 P0-2: at <768px restore ts/actor/ip/WORM as a stacked meta block
   under the row text. Hiding the LGPD-required WORM cell was a Section 7.3
   regression flagged in the audit ("LGPD-required column gone"). */
@media (max-width: 767px) {
  .audit-row {
    grid-template-columns: 1fr;
    gap: var(--space-2);
    padding: var(--space-3);
    border: 1px solid var(--border-1);
    border-radius: 10px;
    margin-bottom: var(--space-2);
  }
  .audit-row > * { display: block; text-align: left; }
  .audit-ico {
    display: inline-grid;
    margin-right: var(--space-2);
    vertical-align: middle;
  }
  .audit-ts {
    font-size: var(--fs-xs);
    color: var(--fg-4);
    margin-bottom: 2px;
  }
  .audit-actor {
    font-size: var(--fs-sm);
    font-weight: var(--fw-700);
    color: var(--fg-1);
    white-space: normal;
  }
  .audit-text { font-size: var(--fs-sm); }
  /* R396 (player-rework / A2 declutter) — mobile `.audit-worm-cell` stack rule
     removed with the WORM column. */
  .audit-ip {
    font-size: var(--fs-xs);
    color: var(--fg-4);
    text-align: left;
  }
}

/* Storage page */
.storage-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 18px;
  align-items: flex-start;
}
@media (max-width: 980px) { .storage-grid { grid-template-columns: 1fr; } }
.storage-usage { position: relative; }
.storage-big-pct {
  font-size: var(--fs-4xl); font-weight: var(--fw-800); color: var(--dnuv-blue-dark);
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
/* W-E/F-DARK: was #B07820 (light-only amber text). The canonical warn-fg
   token flips to a bright amber in dark mode so the big % stays legible. */
.storage-big-pct.warn { color: var(--color-warn-fg); }
.storage-bar {
  width: 100%; height: 10px;
  background: var(--bg-sunken);
  border-radius: 5px;
  overflow: hidden;
}
.storage-bar > span {
  display: block; height: 100%;
  background: linear-gradient(90deg, var(--dnuv-blue-dark), var(--dnuv-blue-light));
  border-radius: 5px;
}
.storage-bar > span.warn { background: linear-gradient(90deg, #B07820, #F59E0B); }
.storage-breakdown { display: flex; flex-direction: column; gap: var(--space-3); margin-top: 4px; }
.storage-bucket { display: flex; flex-direction: column; gap: 5px; }
.storage-bucket-head { display: flex; justify-content: space-between; font-size: var(--fs-sm); }
.storage-bucket-name { color: var(--fg-2); font-weight: var(--fw-500); }
.storage-bucket-size { color: var(--fg-4); font-variant-numeric: tabular-nums; }
.storage-bucket-bar { height: 6px; background: var(--bg-sunken); border-radius: 3px; overflow: hidden; }
.storage-bucket-bar > span { display: block; height: 100%; background: var(--dnuv-blue-light); border-radius: 3px; }
.clip-thumb-sm {
  width: 56px; height: 32px;
  border-radius: 6px;
  background: linear-gradient(135deg, #cfe0f5 0%, #b9d2ee 50%, #94b6df 100%);
  position: relative; overflow: hidden;
  flex: 0 0 56px;
  border: 1px solid rgba(10,31,94,.06);
}

/* ===== Billing v2 (Stripe-integrated layout) ===== */
/* R396 (player-rework / A1 declutter) — the `.bill-status-strip` status bar
   (+ `.bill-status-item/-lab/-val/-sep/-actions` children and its 820px @media
   block) was removed: Billing.js no longer renders it after the A1 declutter
   collapsed the redundant status strip into the plan-header snapshot below.
   Grep confirmed zero remaining references in any kit JS. */
.bill-head-snap { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; font-variant-numeric: tabular-nums; }
.bill-head-sep { color: var(--fg-4); }
.bill-overview-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-top: 14px; }

/* Stripe portal banner */
.bill-stripe-banner {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: linear-gradient(135deg, #635BFF 0%, #4A5EE8 100%);
  color: #fff;
  border-radius: 10px;
  font-size: var(--fs-small);
}
.bill-stripe-banner i { width: 18px; height: 18px; flex: 0 0 18px; }
.bill-stripe-banner .stripe-text { flex: 1; min-width: 0; }
.bill-stripe-banner .stripe-text b { font-weight: var(--fw-700); }
.bill-stripe-banner .stripe-text span { display: block; font-size: var(--fs-sm); opacity: 0.85; margin-top: 2px; }
.bill-stripe-banner .stripe-portal-btn {
  background: #fff; color: #635BFF;
  border: none; border-radius: 7px;
  padding: 7px 12px;
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-700);
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  white-space: nowrap;
  transition: background 120ms;
}
.bill-stripe-banner .stripe-portal-btn:hover { background: #EEF; }
.bill-stripe-banner .stripe-portal-btn i { width: 13px; height: 13px; }
.bill-stripe-logo { font-family: -apple-system, sans-serif; font-weight: var(--fw-800); letter-spacing: -.02em; }

/* Plan grid (3 tiles) */
.bill-plan-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}
@media (max-width: 980px) { .bill-plan-grid { grid-template-columns: 1fr; } }
.bill-plan-tile {
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-card);
  padding: 22px 20px;
  display: flex; flex-direction: column; gap: var(--space-4);
  position: relative;
  box-shadow: var(--shadow-card);
  transition: border-color 120ms, box-shadow 120ms;
}
.bill-plan-tile:hover { border-color: var(--dnuv-blue-200); }
.bill-plan-tile.current {
  border-color: var(--dnuv-blue-dark);
  box-shadow: 0 0 0 1px var(--dnuv-blue-dark), 0 8px 22px rgba(0,34,140,.10);
}
.bill-plan-badge {
  position: absolute; top: -10px; left: 20px;
  display: inline-flex; align-items: center; gap: var(--space-1);
  background: var(--dnuv-blue-dark); color: #fff;
  padding: 4px 10px; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: var(--fw-700);
}
.bill-plan-badge i { width: 12px; height: 12px; }
.bill-plan-tile-head h3 { font-size: var(--fs-lg); margin: 0; font-weight: var(--fw-700); color: var(--fg-1); letter-spacing: -.01em; }
.bill-plan-tile-price { font-size: var(--fs-2xl); font-weight: var(--fw-800); color: var(--fg-1); margin-top: 4px; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.bill-plan-tile-price small { font-size: var(--fs-small); font-weight: var(--fw-500); color: var(--fg-4); margin-left: 2px; }
.bill-plan-tile-limits, .bill-plan-tile-features {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-2);
  font-size: var(--fs-small); color: var(--fg-2);
}
.bill-plan-tile-limits { padding-top: 12px; border-top: 1px solid var(--border-1); }
.bill-plan-tile-limits li, .bill-plan-tile-features li { display: flex; align-items: center; gap: var(--space-2); }
.bill-plan-tile-limits li i { width: 14px; height: 14px; color: var(--fg-4); }
.bill-plan-tile-features li i { width: 14px; height: 14px; color: var(--dnuv-success); }
.bill-plan-tile-foot { margin-top: auto; }

/* Cycle picker */
.bill-cycle-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
@media (max-width: 720px) { .bill-cycle-row { grid-template-columns: 1fr; } }
.bill-cycle-opt {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 14px 16px;
  border: 1px solid var(--border-1);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color 120ms, background 120ms;
}
.bill-cycle-opt:hover { border-color: var(--dnuv-blue-200); background: var(--bg-app); }
.bill-cycle-opt.active { border-color: var(--dnuv-blue-dark); background: var(--dnuv-blue-50); }
.bill-cycle-opt input { margin-top: 3px; accent-color: var(--dnuv-blue-dark); }
.bill-cycle-opt > div { display: flex; flex-direction: column; gap: 2px; }
.bill-cycle-opt b { font-size: var(--fs-base); color: var(--fg-1); display: inline-flex; align-items: center; gap: var(--space-2); }
.bill-cycle-opt span { font-size: var(--fs-sm); color: var(--fg-3); }
.tag-saving {
  display: inline-flex; align-items: center;
  height: 18px; padding: 0 7px; border-radius: 999px;
  /* R267 (W4-a/U4-P0-4) — Status-pill drift removed; canonical tokens. */
  background: var(--dnuv-success-bg); color: var(--color-success);
  font-size: var(--fs-xs); font-weight: var(--fw-800);
}

/* Egress chart */
.bill-egress-chart { display: flex; align-items: flex-end; gap: var(--space-1); height: 120px; padding: 6px 0; }
.bill-egress-bar {
  flex: 1; min-width: 8px;
  background: linear-gradient(180deg, var(--dnuv-blue-light) 0%, var(--dnuv-blue-dark) 100%);
  border-radius: 4px 4px 0 0;
  cursor: pointer; min-height: 4px;
  transition: opacity 120ms;
}
.bill-egress-bar:hover { opacity: 0.7; }
.bill-egress-axis { display: flex; justify-content: space-between; margin-top: 6px; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--fg-4); }

/* Payment methods rows */
.bill-method-row {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 16px;
  background: var(--bg-app);
  border: 1px solid var(--border-1);
  border-radius: 10px;
  margin-bottom: 8px;
}
.bill-method-row.default { background: var(--dnuv-blue-50); border-color: var(--dnuv-blue-200); }
.bill-method-row:last-child { margin-bottom: 0; }
/* `.bill-card-brand` + `.bill-card-info` canonical definitions live further
   down in this file (single source). Earlier 48×30 variant removed in
   W3-e (U4 P0-U4-5) to eliminate dual-definition drift. W3-c font tokens
   applied to the canonical block below. */

/* ===== Recordings cards ===== */
.rec-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 14px;
  padding: 18px;
}
.rec-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-card);
  overflow: hidden;
  cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
/* R267 (W4-a/U4-P0-3) — Canonical hover shape; the cross-cutting rule in the
   .cam-card, .rec-card block (post-R258) also applies translateY(-2px). */
.rec-card:hover { border-color: var(--dnuv-blue-dark); box-shadow: var(--shadow-card-hover); transform: translateY(-2px); }
.rec-thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  background: linear-gradient(135deg, #cfe0f5 0%, #b9d2ee 50%, #94b6df 100%);
  overflow: hidden;
}
.rec-thumb .noise {
  position: absolute; inset: 0;
  opacity: 0.4;
  background-image: radial-gradient(circle at 30% 30%, rgba(255,255,255,.3) 0%, transparent 50%);
}
.rec-thumb .grid-overlay {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(10,31,94,0.10) 1px, transparent 1px),
    linear-gradient(90deg, rgba(10,31,94,0.10) 1px, transparent 1px);
  background-size: 40px 40px;
}
.rec-thumb .rec-play {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 44px; height: 44px;
  border-radius: 50%;
  background: rgba(6,21,70,0.72);
  color: #fff;
  display: grid; place-items: center;
  backdrop-filter: blur(8px);
}
.rec-thumb .rec-play i { width: 18px; height: 18px; }
.rec-thumb .rec-dur {
  position: absolute; bottom: 8px; right: 8px;
  background: rgba(6,21,70,0.85); color: #fff;
  padding: 3px 8px; border-radius: 5px;
  /* R437 (B2, R2): the duration pill is a human-readable label ("9 min 41 s"),
     not an ID/token — drop the mono face so it renders in the body sans like
     every other card label. tabular-nums keeps the digits aligned. */
  font-size: var(--fs-xs); font-weight: var(--fw-600);
  font-variant-numeric: tabular-nums;
}
.rec-card-body { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 6px; }
.rec-card-name { font-size: var(--fs-base); font-weight: var(--fw-700); color: var(--fg-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rec-card-meta { font-size: var(--fs-sm); color: var(--fg-4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rec-card-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 4px; }

/* ===== Timeline ===== */
.timeline-wrap {
  padding: 18px;
  display: flex; flex-direction: column; gap: var(--space-1);
}
.timeline-head {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: var(--space-3);
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border-1);
  margin-bottom: 8px;
}
.timeline-hours {
  display: grid;
  grid-template-columns: repeat(24, 1fr);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--fg-4);
}
.timeline-hours .th-cell { text-align: left; padding-left: 2px; }
.timeline-row {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: var(--space-3);
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px dashed rgba(10,31,94,.06);
}
.timeline-row:last-of-type { border-bottom: none; }
.timeline-row-head { display: flex; align-items: center; gap: 10px; }
.timeline-row-info { flex: 1; min-width: 0; }
.timeline-row-info b { display: block; font-size: var(--fs-small); color: var(--fg-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.timeline-row-info .row-meta { font-size: var(--fs-xs); }
.rec-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--dnuv-danger);
  box-shadow: 0 0 0 3px rgba(229,72,77,.15);
  animation: rec-pulse 1.4s infinite;
}
@keyframes rec-pulse {
  0%,100% { opacity: 1; }
  50%     { opacity: 0.5; }
}
.timeline-track {
  position: relative;
  height: 28px;
  background: var(--bg-sunken);
  border-radius: 6px;
  overflow: hidden;
}
.tl-seg {
  position: absolute; top: 3px; bottom: 3px;
  border-radius: 4px;
  cursor: pointer;
  transition: opacity 120ms;
}
.tl-seg:hover { opacity: 0.75; }
.tl-seg.continuous { background: var(--dnuv-blue-light); }
.tl-seg.motion     { background: #F2B431; }
.tl-seg.incident   { background: var(--dnuv-danger); box-shadow: 0 0 0 2px rgba(229,72,77,.15); }
.timeline-now {
  position: absolute; top: -4px; bottom: -4px;
  width: 2px;
  background: var(--dnuv-danger);
  z-index: 2;
}
.timeline-now::before {
  content: ""; position: absolute;
  top: -3px; left: -3px;
  width: 8px; height: 8px;
  background: var(--dnuv-danger);
  border-radius: 50%;
}
.timeline-legend {
  display: flex; align-items: center; gap: var(--space-4);
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--border-1);
  font-size: var(--fs-sm); color: var(--fg-3);
  flex-wrap: wrap;
}
.timeline-legend .legend-item { display: inline-flex; align-items: center; gap: 6px; }
.timeline-legend .legend-item .dot { width: 14px; height: 8px; border-radius: 3px; }
.timeline-legend .legend-item .dot.continuous { background: var(--dnuv-blue-light); }
.timeline-legend .legend-item .dot.motion     { background: #F2B431; }
.timeline-legend .legend-item .dot.incident   { background: var(--dnuv-danger); }
.timeline-legend .legend-item .dot.none       { background: var(--bg-sunken); border: 1px solid var(--border-1); }
.timeline-legend .legend-meta { margin-left: auto; color: var(--fg-4); }

/* ===== Live View ===== */
.live-shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 14px;
  align-items: flex-start;
}
@media (max-width: 1100px) { .live-shell { grid-template-columns: 1fr; } }

/* AUDIT redesign — the player is a single self-contained frame. The stage IS
   the card (no separate white head/controls bars); the LIVE chip, inline
   state banner and the one control bar are overlaid inside it. */
.live-player {
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  display: flex; flex-direction: column;
}
.live-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(10,31,94,.92); color: #fff;
  font-size: var(--fs-xs); font-weight: var(--fw-800); letter-spacing:var(--ls-wide);
  padding: 5px 10px; border-radius: 999px;
}
.live-chip .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--dnuv-blue-light);
  animation: pulse 1.4s infinite ease-in-out;
}
.live-player-stage {
  position: relative;
  aspect-ratio: 16 / 9;
  background: linear-gradient(135deg, #cfe0f5 0%, #b9d2ee 50%, #94b6df 100%);
  overflow: hidden;
  border-radius: var(--radius-card);
}
.live-player-stage video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  background: #06133D;
}
.live-player-stage .noise {
  position: absolute; inset: 0;
  opacity: 0.4;
  background-image: radial-gradient(circle at 30% 30%, rgba(255,255,255,.3) 0%, transparent 50%);
}
.live-player-stage .grid-overlay {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(10,31,94,0.10) 1px, transparent 1px),
    linear-gradient(90deg, rgba(10,31,94,0.10) 1px, transparent 1px);
  background-size: 40px 40px;
}
/* R396 flicker fix — the overlay layer wraps ALL chrome (chip, label, banner,
   PTZ, stats, control bar, settings) so it can be re-rendered IN PLACE without
   touching the sibling <video>. It fills the stage so its absolutely-positioned
   children anchor exactly as before; pointer-events:none lets clicks fall
   through the empty areas to the video, while each interactive child re-enables
   pointer-events:auto. The offline variant hosts the (interactive) offline
   panel, so it re-enables events on itself. */
.live-stage-overlays {
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none;
}
/* Re-enable pointer events only on the interactive overlay surfaces (the
   decorative chip + cam label stay click-through via their own rules). */
.live-stage-overlays .live-overlay-controls,
.live-stage-overlays .ptz-overlay,
.live-stage-overlays .live-stats-panel,
.live-stage-overlays .live-cfg-pop,
.live-stage-overlays .live-state-banner,
.live-stage-overlays .player-cap-modal.open { pointer-events: auto; }
.live-stage-overlays-offline { pointer-events: auto; }

/* LIVE / history chip — top-left overlay inside the stage. */
.live-chip-overlay {
  position: absolute; top: 12px; left: 12px; z-index: 4;
  box-shadow: 0 2px 8px rgba(6,19,61,.28);
}

/* R397 v2 (DECLUTTER-1d) — the in-video "Câmera X · <datetime>" overlay
   (.live-cam-label) was removed (the header already shows the name and the chip
   announces temporal state; the overlay was pure duplication). Its CSS is
   dropped here since LiveView.js no longer emits the element. */

.live-offline-state {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  color: var(--fg-4);
  background: var(--bg-sunken);
  z-index: 3;
}
.live-offline-state i { width: 32px; height: 32px; }
/* Wave D #53 — offline-as-OVERLAY styling lives after the "Offline state
   extended" block below (so its white-on-dark overrides win on source order). */

/* ── Single overlaid control bar (absolute, bottom of stage) ──────────────
   R396 — YouTube-style hover-reveal: a dark-but-transparent gradient scrim with
   clean WHITE hollow icon controls. At rest the whole bar (scrim + controls) is
   faded out (data-shown="0") so the video reads clean; on hover/focus/touch it
   fades in. Visibility is driven by JS via the data-shown attribute (set on
   activity) / .chrome-idle on the stage. The scrim is intentionally darker at
   the very bottom so the white glyphs stay legible over bright video. */
.live-overlay-controls {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 6;
  padding: 38px 16px 12px;
  background: linear-gradient(to top, rgba(6,19,61,.82) 0%, rgba(6,19,61,.52) 38%, rgba(6,19,61,.12) 78%, rgba(6,19,61,0) 100%);
  opacity: 1; transform: translateY(0);
  transition: opacity .28s var(--ease-standard), transform .28s var(--ease-standard);
}
.live-overlay-controls[data-shown="0"] { opacity: 0; transform: translateY(6px); pointer-events: none; }
/* R393/R396 — three calm clusters (left / center / right), evizz / Visionary.ai
   spirit, instead of one dense 11-control strip. The center cluster owns the
   transport + scrub line and grows to fill; the side clusters hug their edges.
   align-items:center keeps every glyph + the slider + scrub line on one optical
   baseline (alignment fix). */
.live-overlay-row {
  display: flex; align-items: center; gap: var(--space-4);
  justify-content: space-between;
}
.live-overlay-cluster { display: flex; align-items: center; gap: 6px; }
.live-overlay-center { flex: 1; min-width: 0; gap: 10px; }
.live-overlay-left, .live-overlay-right { flex: 0 0 auto; }
.live-overlay-right { gap: 4px; }
.live-overlay-stat {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  color: rgba(255,255,255,.78); white-space: nowrap; padding: 0 2px;
}
/* R396 — clean WHITE hollow icon buttons (YouTube-like): no box / heavy
   outline. White glyph, transparent background, subtle round hover wash. A soft
   text-shadow keeps the glyph legible the instant the scrim begins to fade. */
.live-overlay-controls .btn-icon {
  width: 36px; height: 36px;
  background: transparent;
  border: none;
  border-radius: 50%;
  display: grid; place-items: center;
  cursor: pointer;
  color: #fff;
  padding: 0;
  filter: drop-shadow(0 1px 2px rgba(6,19,61,.55));
  transition: background 140ms var(--ease-standard), color 140ms var(--ease-standard), transform 100ms var(--ease-standard);
}
.live-overlay-controls .btn-icon:hover { background: rgba(255,255,255,.16); }
.live-overlay-controls .btn-icon:active { transform: scale(.94); }
/* Active/“on” states tint the glyph itself (no filled box) so the bar stays
   light: brand blue for live/audio toggles, success green for two-way talk. */
.live-overlay-controls .btn-icon.active { color: var(--dnuv-blue-light); background: rgba(255,255,255,.10); }
.live-overlay-controls .btn-icon.talk.active { color: var(--dnuv-success); background: rgba(255,255,255,.10); }
.live-overlay-controls .btn-icon i { width: 20px; height: 20px; }
/* Play/pause is the primary transport — render it a touch larger, YouTube-like. */
.live-overlay-controls .lv-action-playpause i { width: 26px; height: 26px; }
.live-overlay-controls .live-time {
  color: rgba(255,255,255,.92); text-shadow: 0 1px 2px rgba(6,19,61,.6);
  font-size: var(--fs-sm); white-space: nowrap;
}
/* Wave D #54 — clear AO VIVO indicator/button (replaces the cryptic ⊙ glyph).
   When live (.is-live) it is a calm red pill with a pulsing dot that just
   confirms the state; in VOD it dims to a neutral "voltar ao vivo" action. */
.lv-live-btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 12px; margin-left: 4px;
  border: 1px solid rgba(255,255,255,.28);
  border-radius: 999px;
  background: rgba(255,255,255,.10);
  color: #fff; font-family: inherit; font-size: var(--fs-xs); font-weight: var(--fw-700);
  letter-spacing: var(--ls-wide); text-transform: uppercase;
  cursor: pointer; white-space: nowrap;
  filter: drop-shadow(0 1px 2px rgba(6,19,61,.55));
  transition: background 140ms var(--ease-standard), border-color 140ms var(--ease-standard);
}
.lv-live-btn:hover { background: rgba(255,255,255,.18); }
.lv-live-btn .lv-live-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: rgba(255,255,255,.55); flex: 0 0 8px;
}
/* Live now: solid red dot (pulsing) + brand-red tint, reads as "on air". */
.lv-live-btn.is-live {
  border-color: rgba(239,68,68,.55);
  background: rgba(239,68,68,.18);
  cursor: default;
}
.lv-live-btn.is-live .lv-live-dot {
  background: #ef4444;
  box-shadow: 0 0 0 0 rgba(239,68,68,.65);
  animation: lv-live-pulse 2s ease-out infinite;
}
@keyframes lv-live-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(239,68,68,.6); }
  70%  { box-shadow: 0 0 0 6px rgba(239,68,68,0); }
  100% { box-shadow: 0 0 0 0 rgba(239,68,68,0); }
}
@media (prefers-reduced-motion: reduce) {
  .lv-live-btn.is-live .lv-live-dot { animation: none; }
}
.lv-live-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* ── Inline player-state banner (the single error surface) ────────────────
   Centered inside the stage; replaces the toast flood for stream/player
   errors. Hidden via [hidden]; tone via data-tone. */
.live-state-banner {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 5;
  display: inline-flex; align-items: center; gap: 10px;
  max-width: calc(100% - 32px);
  padding: 10px 16px;
  border-radius: 999px;
  background: rgba(6,19,61,.88);
  color: #fff; font-size: var(--fs-sm); font-weight: var(--fw-600);
  box-shadow: 0 6px 24px rgba(6,19,61,.4);
  backdrop-filter: blur(2px);
}
.live-state-banner[hidden] { display: none; }
.live-state-banner[data-tone="danger"] { background: rgba(127,29,29,.92); }
.live-state-banner .live-state-ico { width: 16px; height: 16px; }
.live-state-banner .live-state-msg { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.live-state-spinner {
  width: 15px; height: 15px; flex: 0 0 15px;
  border: 2px solid rgba(255,255,255,.30);
  border-top-color: #fff;
  border-radius: 50%;
  animation: lv-spin .8s linear infinite;
}
@keyframes lv-spin { to { transform: rotate(360deg); } }
.live-state-retry {
  display: inline-flex; align-items: center; gap: 5px;
  margin-left: 4px; padding: 4px 10px;
  border: 1px solid rgba(255,255,255,.28);
  border-radius: 999px;
  background: rgba(255,255,255,.10);
  color: #fff; font-size: var(--fs-xs); font-weight: var(--fw-700);
  cursor: pointer;
}
.live-state-retry[hidden] { display: none; }
.live-state-retry:hover { background: rgba(255,255,255,.20); }
.live-state-retry i { width: 12px; height: 12px; }

/* ── R402 WAN cap — "Ainda está aí?" modal + frozen-frame ──────────────────
   Ported from the proven dashboard.html (.player-cap-modal / .player-cap-card),
   restyled to CENTER over the kit stage (the stage is the focal element here,
   vs. the dashboard's bottom-right corner). Shown only when _stream-cap.js adds
   `.open` after the cloud session cap is reached; the rest of the time it is
   display:none and click-through. */
.live-cap-freeze {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  background: #06133D;
  z-index: 3; /* above the <video>/placeholders, below the overlay layer (z=2 host, but banner/modal sit higher) */
}
.live-cap-freeze[hidden] { display: none; }
.player-cap-modal {
  position: absolute; inset: 0;
  z-index: 6; /* above the freeze frame + state banner */
  display: none;
  align-items: center; justify-content: center;
  padding: 16px;
  background: rgba(6,19,61,.55);
  backdrop-filter: blur(3px);
  pointer-events: auto;
}
.player-cap-modal.open { display: flex; }
.player-cap-card {
  width: min(360px, calc(100% - 32px));
  border: 1px solid rgba(255,255,255,.18);
  border-radius: var(--radius-card, 12px);
  background: rgba(18,23,35,.96);
  box-shadow: 0 18px 48px rgba(0,0,0,.5);
  padding: 22px;
  text-align: center;
}
.player-cap-title { font-size: var(--fs-md, 16px); font-weight: var(--fw-700, 800); margin-bottom: 8px; color: #fff; }
.player-cap-sub { font-size: var(--fs-sm, 13px); color: rgba(255,255,255,.72); line-height: var(--lh-relaxed); margin-bottom: 16px; }
.player-cap-btn {
  width: 100%; border: none;
  border-radius: 8px;
  background: linear-gradient(135deg, #3b7eff, #2563eb);
  color: #fff; font-size: var(--fs-sm, 13px); font-weight: var(--fw-700, 800);
  font-family: inherit; cursor: pointer;
  padding: 11px 14px;
}
.player-cap-btn:hover { filter: brightness(1.08); }
.player-cap-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* ── Native fullscreen: the maximized stage reuses the same overlay ───────
   When promoted via the Fullscreen API the stage fills the viewport and the
   16:9 aspect-ratio constraint is dropped so the video uses all the space. */
.live-player-stage:fullscreen,
.live-player-stage.is-fullscreen {
  aspect-ratio: auto;
  width: 100vw; height: 100vh;
  border-radius: 0;
  background: #06133D;
}
.live-player-stage:fullscreen .live-overlay-controls,
.live-player-stage.is-fullscreen .live-overlay-controls { padding: 40px 28px 22px; }
.live-player-stage:fullscreen .live-overlay-controls .btn-icon,
.live-player-stage.is-fullscreen .live-overlay-controls .btn-icon { width: 40px; height: 40px; }
.live-player-stage:fullscreen .live-chip-overlay,
.live-player-stage.is-fullscreen .live-chip-overlay { top: 20px; left: 20px; }

/* Wave D #52 — the in-overlay .live-track scrub line + its segment/playhead
   rules were REMOVED with the duplicate timeline. The ONE timeline is the
   day-scoped strip below the video (.lv-strip*), which now also hosts the seek
   playhead and the clip-range A/B markers. The .live-time label below still
   sits in the control-bar center cluster. */
.live-time {
  font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--fg-3);
  white-space: nowrap; padding: 0 4px;
}

/* Live sidebar */
.live-side {
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-card);
  display: flex; flex-direction: column;
  /* W10/U13 P0 — dvh fallback for iOS Safari address-bar collapse. */
  max-height: calc(100vh - 220px);
  max-height: calc(100dvh - 220px);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.live-side-head {
  padding: 14px 16px 10px;
}
.live-side-head h3 { margin: 0; font-size: var(--fs-base); font-weight: var(--fw-700); color: var(--fg-1); }
.live-side-head .row-meta { font-size: var(--fs-sm); margin-top: 2px; display: block; }
.live-side-search {
  margin: 0 14px 10px;
  display: flex; align-items: center; gap: var(--space-2);
  padding: 0 10px;
  height: 32px;
  border: 1px solid var(--border-1);
  border-radius: 7px;
  background: var(--bg-app);
}
.live-side-search:focus-within { border-color: var(--dnuv-blue-light); }
.live-side-search i { width: 13px; height: 13px; color: var(--fg-4); }
.live-side-search input { flex: 1; border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--fs-sm); min-width: 0; }
.live-cam-list { flex: 1; overflow-y: auto; padding: var(--space-1) var(--space-2) var(--space-3); display: flex; flex-direction: column; gap: 2px; }
.live-cam-row {
  display: flex; align-items: center; gap: 10px;
  padding: var(--space-2) var(--space-2);
  border-radius: 8px;
  cursor: pointer;
  transition: background 100ms;
}
.live-cam-row:hover { background: var(--bg-sunken); }
.live-cam-row.active { background: var(--dnuv-blue-50); }
.cam-thumb-mini {
  width: 48px; height: 28px;
  border-radius: 4px;
  background: linear-gradient(135deg, #cfe0f5, #94b6df);
  position: relative; overflow: hidden;
  flex: 0 0 48px;
  display: grid; place-items: center;
  color: var(--fg-4);
}
.cam-thumb-mini i { width: 14px; height: 14px; }
.cam-thumb-mini .noise {
  position: absolute; inset: 0;
  background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.3) 0%, transparent 50%);
}
.cam-thumb-mini.offline { background: var(--bg-sunken); }
/* Wave D #8 (LiveView family) — the camera-row selectable target is a real
   <button> (keyboard-operable accessible name). Chrome-RESET it so the browser
   default button box (border / grey background / inset padding / centered text)
   never frames the camera name: it must read as a plain row label, identical to
   the old clickable <div>. */
.live-cam-info {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: flex-start;
  appearance: none; -webkit-appearance: none;
  margin: 0; padding: 0;
  border: none; background: none;
  font: inherit; color: inherit; text-align: left;
  cursor: pointer;
}
.live-cam-info b { font-size: var(--fs-sm); color: var(--fg-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.live-cam-info span { font-size: var(--fs-xs); color: var(--fg-4); }
.live-cam-info:focus-visible { outline: var(--ring-focus); outline-offset: 2px; border-radius: 6px; }
.live-mode-toggle { height: 36px; align-items: center; }
.live-mode-toggle button i { width: 14px; height: 14px; }

/* ===== Multiview layouts grid ===== */
.multiview-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-3);
}
.layout-card {
  background: var(--bg-app);
  border: 1px solid var(--border-1);
  border-radius: 10px;
  padding: var(--space-3);
  cursor: pointer;
  transition: border-color 120ms, background 120ms;
}
.layout-card:hover { border-color: var(--dnuv-blue-200); background: var(--bg-surface); }
.layout-card.active { border-color: var(--dnuv-blue-dark); background: var(--dnuv-blue-50); }
.layout-thumb {
  display: grid; gap: 2px;
  width: 100%; aspect-ratio: 16 / 9;
  background: var(--bg-sunken);
  border-radius: 5px;
  padding: var(--space-1);
  margin-bottom: 10px;
}
.layout-thumb.m-1 { grid-template-columns: 1fr; grid-template-rows: 1fr; }
.layout-thumb.m-2 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.layout-thumb.m-3 { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); }
.layout-thumb.m-4 { grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(4, 1fr); }
/* Wave F (#2) — 5×5 (25-slot) layouts in the card thumbnail too. */
.layout-thumb.m-5 { grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(5, 1fr); }
/* W7-v2/U16 #13 — spotlight layouts in thumbnails too. */
.layout-thumb.m-1p5 { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); grid-auto-flow: dense; }
.layout-thumb.m-1p5 > .cell:first-child { grid-column: span 2; grid-row: span 2; }
.layout-thumb.m-1p7 { grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(3, 1fr); grid-auto-flow: dense; }
.layout-thumb.m-1p7 > .cell:first-child { grid-column: span 2; grid-row: span 2; }
.layout-thumb .cell { background: var(--border-1); border-radius: 2px; }
.layout-thumb .cell.full { background: linear-gradient(135deg, var(--dnuv-blue-light), var(--dnuv-blue-dark)); }

/* W7-v2/U16 #13 — Multiview layout switcher toolbar.
   Segmented control showing 1×1 / 2×2 / 3×3 / 4×4 / 1+5 / 1+7 options.
   Mirrors the existing .seg-ctrl pattern (kit/index.html:713) plus a wider
   active state and 44×44 hit-target on touch viewports. */
.mv-layout-switcher {
  display: inline-flex;
  background: var(--bg-sunken);
  border-radius: 10px;
  padding: 3px;
  gap: 2px;
  flex-wrap: wrap;
}
.mv-layout-switcher button {
  border: none;
  background: transparent;
  padding: 8px 12px;
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-700);
  color: var(--fg-3);
  border-radius: 8px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
}
/* Wave F (multiview audit #6) — the old hover used a hardcoded
   rgba(255,255,255,.6) wash + --fg-1 text. On the dark theme that paints
   near-white text on a near-white wash → illegible. Use the tokenized raised
   surface (one notch above the --bg-sunken switcher track) with strong text;
   both flip correctly per theme. */
.mv-layout-switcher button:hover:not(.active) { background: var(--bg-surface); color: var(--fg-1); }
.mv-layout-switcher button.active {
  background: var(--dnuv-blue-50);
  color: var(--dnuv-blue-dark);
  box-shadow: 0 1px 3px rgba(10,31,94,.08);
}
.mv-layout-switcher button:focus-visible {
  outline: 2px solid var(--dnuv-blue-dark);
  outline-offset: 1px;
}

/* ===== Multiview mosaic — desktop grid (Wave F, audit #2/#3) =====
   The base `.mosaic` + `.m-2/.m-3/.m-4` desktop grids live in index.html (the
   SERVED shell). The tokens BELOW were never defined there, so on desktop they
   fell back to `display:grid` with a single implicit column:
     - m-1p5 / m-1p7 (the spotlight layouts) COLLAPSED to one column (#3);
     - m-1 (solo) + m-5 (25-slot, new in Wave F #2) had no explicit template.
   Define them here (this file owns the Multiview rule sections). The phone
   breakpoint further down (max-width:767px) still collapses every multi-col
   mosaic to a single column. */
.mosaic.m-1 { grid-template-columns: 1fr; }
.mosaic.m-5 { grid-template-columns: repeat(5, 1fr); }
/* Spotlight: one large primary tile + a ring of smaller tiles. dense flow lets
   the remaining tiles backfill around the 2×2 hero so there are no holes. */
.mosaic.m-1p5 {
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 1fr;
  grid-auto-flow: dense;
}
.mosaic.m-1p5 > .tile:first-child { grid-column: span 2; grid-row: span 2; }
.mosaic.m-1p7 {
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 1fr;
  grid-auto-flow: dense;
}
.mosaic.m-1p7 > .tile:first-child { grid-column: span 2; grid-row: span 2; }
/* The hero tile spans two rows, so its intrinsic 16/9 aspect would force a very
   tall row. Let the spanning tile fill the two auto-rows instead of dictating
   their height (the small tiles keep their 16/9 ratio). */
.mosaic.m-1p5 > .tile:first-child,
.mosaic.m-1p7 > .tile:first-child { aspect-ratio: auto; height: 100%; }

/* ===== Multiview fullscreen (Wave F, audit #4) =====
   "Tela cheia" maximizes the whole wall (.mosaic). Native Fullscreen API is
   preferred; this is the CSS-maximize fallback for kit preview / browsers
   without the API (mirrors LiveView's .lv-fs-css). The same .mosaic node is
   fixed over the viewport above modals/toasts — no relocation, no separate
   markup. */
.mosaic.mv-fs-css {
  position: fixed;
  inset: 0;
  /* Parity with LiveView's .lv-fs-css (kit/index.html, z-index:320) — above
     toasts (200) / menus, the sibling fullscreen surface convention. */
  z-index: 320;
  margin: 0;
  padding: 16px;
  background: var(--bg-app);
  overflow: auto;
  border-radius: 0;
}
/* When native fullscreen is active, the :fullscreen element fills the screen on
   its own; just give it a background + padding so the tiles aren't edge-glued. */
.mosaic:fullscreen,
.mosaic:-webkit-full-screen {
  padding: 16px;
  background: var(--bg-app);
}

/* W7-v2/U16 #8 — Clip range markers on the live-track.
   Start / end handles overlay the existing playhead so the user can pick a
   from/to window by clicking "Definir início" / "Definir fim" or dragging
   the handles. */
/* Wave D — the clip-range A/B export markers now live INSIDE the strip track
   (the ONE timeline), which is 64px tall with overflow:hidden. So the bars span
   the full track height (top/bottom 0) and the A/B label sits INSIDE the track
   (top:3px) instead of floating above it (where the track's overflow would clip
   it). Markers sit above the clip cards but below the seek playhead. */
.clip-range-mark {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--dnuv-blue-dark);
  border-radius: 2px;
  pointer-events: auto;
  cursor: ew-resize;
  z-index: 3;
}
.clip-range-mark::after {
  content: attr(data-label);
  position: absolute;
  top: 3px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--dnuv-blue-dark);
  color: #fff;
  font-size: var(--fs-xs);
  font-weight: var(--fw-700);
  padding: 1px 5px;
  border-radius: 4px;
  white-space: nowrap;
  pointer-events: none;
}
.clip-range-mark.end { background: var(--dnuv-danger); }
.clip-range-mark.end::after { background: var(--dnuv-danger); }
.clip-range-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  background: rgba(60,179,254,.22);
  border-radius: 0;
  pointer-events: none;
  z-index: 2;
}
.clip-range-summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--fg-2);
  padding: 4px 10px;
  background: var(--bg-sunken);
  border-radius: 6px;
}
.clip-range-summary .arrow { color: var(--fg-4); }
.clip-range-summary .dur { color: var(--dnuv-blue-dark); font-weight: var(--fw-700); }
.clip-range-summary.empty { color: var(--fg-4); }

.clip-range-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 0 4px;
}
.clip-range-toolbar .spacer { flex: 1; }

/* ===== R393 — Conceptzilla clip-strip (day-scoped recording timeline) =====
   Sits BELOW the video, distinct from the in-overlay .live-track scrubber. A
   surfaced card with a toolbar (title · day picker · cloud/SD toggle · zoom),
   an hour-tick axis, positioned "N clips" cards, and a draggable playhead.
   Tokens only → light + dark safe. */
.lv-strip {
  margin-top: 12px;
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 12px 14px 14px;
}
.lv-strip-toolbar {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin-bottom: 12px;
}
.lv-strip-title {
  font-size: var(--fs-sm); font-weight: var(--fw-700);
  color: var(--fg-1); letter-spacing: -.01em;
}
/* R397 v2 (DECLUTTER-1f) — the always-on "Arraste para navegar no tempo" hint
   was removed from the strip toolbar (LiveView.js no longer emits
   .lv-strip-hint); its now-dead CSS rule is dropped here. */
.lv-strip-spacer { flex: 1; min-width: 8px; }

/* Day picker */
.lv-strip-day { position: relative; }
.lv-strip-day-btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 10px;
  border: 1px solid var(--border-1); border-radius: 8px;
  background: var(--bg-app); color: var(--fg-1);
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-600);
  cursor: pointer; transition: border-color 120ms, background 120ms;
}
.lv-strip-day-btn:hover { border-color: var(--dnuv-blue-200); background: var(--bg-surface); }
.lv-strip-day-btn i { width: 14px; height: 14px; color: var(--fg-3); }
.lv-strip-day-pop {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 8;
  min-width: 200px;
  display: flex; flex-direction: column; gap: 2px;
  padding: 6px;
  background: var(--bg-surface);
  border: 1px solid var(--border-1); border-radius: 10px;
  box-shadow: var(--shadow-card);
}
.lv-strip-day-pop > button {
  text-align: left;
  padding: 8px 10px; border: none; border-radius: 7px;
  background: transparent; color: var(--fg-2);
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-600);
  cursor: pointer;
}
.lv-strip-day-pop > button:hover { background: var(--bg-sunken); color: var(--fg-1); }
.lv-strip-day-pop > button.active { background: var(--dnuv-blue-50); color: var(--dnuv-blue-dark); }
.lv-strip-day-date {
  display: flex; flex-direction: column; gap: 4px;
  padding: 8px 10px 4px; margin-top: 2px;
  border-top: 1px solid var(--border-1);
}
.lv-strip-day-date > span { font-size: var(--fs-xs); color: var(--fg-4); font-weight: var(--fw-700); text-transform: uppercase; letter-spacing: var(--ls-wide); }
.lv-strip-day-date input {
  height: 30px; padding: 0 8px;
  border: 1px solid var(--border-1); border-radius: 7px;
  background: var(--bg-app); color: var(--fg-1);
  font-family: inherit; font-size: var(--fs-sm);
}

/* R396 — the cloud/SD source toggle (.lv-strip-source) was removed: DNUV is
   cloud-only, so its segmented-control styling is gone with the markup. */

/* Zoom −/+ */
.lv-strip-zoom { display: inline-flex; align-items: center; gap: 6px; }
.lv-strip-zoom button {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border: 1px solid var(--border-1); border-radius: 8px;
  background: var(--bg-app); color: var(--fg-1);
  cursor: pointer; transition: border-color 120ms, background 120ms;
}
.lv-strip-zoom button:hover:not([disabled]) { border-color: var(--dnuv-blue-200); background: var(--bg-surface); }
.lv-strip-zoom button i { width: 15px; height: 15px; }
.lv-strip-zoom-lab {
  font-family: var(--font-mono); font-size: var(--fs-sm);
  color: var(--fg-3); min-width: 30px; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Axis + track */
.lv-strip-axis-wrap { position: relative; }
.lv-strip-axis {
  position: relative; height: 18px; margin-bottom: 6px;
  border-bottom: 1px solid var(--border-1);
}
.lv-strip-tick { position: absolute; top: 0; bottom: 0; transform: translateX(-50%); }
.lv-strip-tick-line {
  position: absolute; bottom: 0; left: 50%;
  width: 1px; height: 6px; background: var(--border-1);
  transform: translateX(-50%);
}
.lv-strip-tick-lab {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  color: var(--fg-4); white-space: nowrap; font-variant-numeric: tabular-nums;
}
.lv-strip-track {
  position: relative;
  height: 64px;
  background: var(--bg-sunken);
  border-radius: 8px;
  overflow: hidden;
  /* R396 — drag-to-scrub: the track itself is grabbable to pan the playhead. */
  cursor: ew-resize;
  touch-action: none;
}
.lv-strip-track .lv-strip-clip { cursor: pointer; }

/* Clip cards positioned along the axis */
.lv-strip-clip {
  position: absolute; top: 8px; bottom: 8px;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 0; border: 1px solid var(--dnuv-blue-200);
  border-radius: 8px; overflow: hidden;
  background: var(--dnuv-blue-50);
  cursor: pointer;
  text-align: left;
  transition: transform 120ms var(--ease-standard), border-color 120ms, box-shadow 120ms;
  max-width: 46%;
}
.lv-strip-clip:hover {
  transform: translateY(-1px);
  border-color: var(--dnuv-blue-dark);
  box-shadow: 0 4px 12px rgba(10,31,94,.14);
  z-index: 3;
}
.lv-strip-clip-thumb {
  position: absolute; inset: 0;
  background: linear-gradient(135deg, #cfe0f5 0%, #b9d2ee 50%, #94b6df 100%);
}
.lv-strip-clip-thumb .noise {
  position: absolute; inset: 0;
  background: radial-gradient(circle at 30% 25%, rgba(255,255,255,.45) 0%, transparent 55%);
}
.lv-strip-clip-meta {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; gap: 4px;
  margin: 0 4px 4px; padding: 2px 6px;
  background: rgba(6,19,61,.82); color: #fff;
  border-radius: 6px;
  font-size: var(--fs-xs); font-weight: var(--fw-700);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: calc(100% - 8px);
}
.lv-strip-clip-meta i { width: 11px; height: 11px; flex: 0 0 11px; }
.lv-strip-clip-lab { overflow: hidden; text-overflow: ellipsis; }

/* R397 v2 (timeline coverage bar) — replaces the overlapping "N clipes" group
   CARDS with a CONTINUOUS COVERAGE BAR: one solid fill per merged stretch of
   recorded footage, real gaps left empty. Each segment is a thin full-height
   button (click → seek+play that instant). Positioned absolutely along the
   track via inline left/width %. */
.lv-strip-seg {
  position: absolute; top: 10px; bottom: 10px;
  appearance: none; -webkit-appearance: none;
  padding: 0; margin: 0;
  border: none;
  border-radius: 4px;
  background: var(--dnuv-blue-500);
  cursor: pointer;
  min-width: 2px;
  transition: background 120ms, box-shadow 120ms;
}
.lv-strip-seg:hover {
  background: var(--dnuv-blue-dark);
  box-shadow: 0 0 0 2px var(--dnuv-blue-200);
  z-index: 3;
}
.lv-strip-seg:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  z-index: 3;
}

/* Skeleton placeholders while loading */
.lv-strip-skeleton { position: absolute; inset: 0; }
.lv-strip-clip.is-skeleton {
  width: 14%; border: none; background: var(--border-1);
  opacity: .55; cursor: default;
  animation: lv-strip-pulse 1.2s ease-in-out infinite;
}
@keyframes lv-strip-pulse { 0%,100% { opacity: .35; } 50% { opacity: .7; } }
.lv-strip-empty {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  margin: 0; padding: 0 16px; text-align: center;
  font-size: var(--fs-sm); color: var(--fg-4);
}

/* Draggable playhead */
.lv-strip-playhead {
  position: absolute; top: -4px; bottom: -4px;
  width: 2px; background: var(--dnuv-danger);
  transform: translateX(-1px);
  z-index: 4; cursor: ew-resize;
}
.lv-strip-playhead-knob {
  position: absolute; top: -3px; left: 50%; transform: translateX(-50%);
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--dnuv-danger);
  border: 2px solid var(--bg-surface);
  box-shadow: 0 1px 3px rgba(0,0,0,.25);
}

/* Focus-visible — canonical ring token (C0 Foundation) */
.lv-strip-day-btn:focus-visible,
.lv-strip-zoom button:focus-visible,
.lv-strip-day-pop > button:focus-visible,
.lv-strip-day-date input:focus-visible,
.lv-strip-clip:focus-visible,
.lv-strip-playhead:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  border-radius: 8px;
  z-index: 5;
}
.lv-strip-playhead:focus-visible { border-radius: var(--radius-pill); }

/* ---------------------------------------------------------------------------
   R412 (H.VOD.1 / H-14) — VOD motion-thumbnail timeline.
   Motion markers (amber) overlay the blue coverage bar so a reviewer can jump
   straight to movement; a hover-scrub tile previews the moment (captured motion
   frame when one is near, time-only otherwise). Amber is distinct from the blue
   coverage fill and the red playhead, and is dark-mode aware via --dnuv-warning.
   --------------------------------------------------------------------------- */

/* The motion markers live in their own absolutely-positioned layer ABOVE the
   coverage fills but BELOW the playhead + clip-range A/B markers. */
.lv-motion-track {
  position: absolute; inset: 0;
  pointer-events: none; /* the marker buttons themselves re-enable pointer */
  z-index: 3;
}
.lv-motion-mark {
  position: absolute; top: 0; bottom: 0;
  width: 14px; transform: translateX(-7px);
  appearance: none; -webkit-appearance: none;
  padding: 0; margin: 0; border: none; background: transparent;
  cursor: pointer; pointer-events: auto;
  display: flex; align-items: flex-start; justify-content: center;
}
/* The visible part is a slim amber stalk + a dot at the top, so the marker
   reads against the blue bar without obscuring the coverage underneath. */
.lv-motion-mark::before {
  content: ""; position: absolute; top: 4px; bottom: 4px; left: 50%;
  width: 2px; transform: translateX(-1px);
  background: var(--dnuv-warning);
  opacity: .85;
  border-radius: 2px;
  transition: opacity 120ms, width 120ms;
}
.lv-motion-dot {
  position: relative; z-index: 1;
  margin-top: 1px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--dnuv-warning);
  border: 2px solid var(--bg-surface);
  box-shadow: 0 1px 2px rgba(0,0,0,.25);
}
.lv-motion-mark:hover::before { opacity: 1; width: 3px; }
.lv-motion-mark:hover .lv-motion-dot { transform: scale(1.15); }
.lv-motion-mark:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  border-radius: var(--radius-pill);
  z-index: 6;
}

/* Toolbar motion-count chip — amber, text + dot (never color-only). */
.lv-strip-motion-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px; border-radius: var(--radius-pill);
  background: var(--dnuv-warning-bg); color: var(--dnuv-warning);
  font-size: var(--fs-xs); font-weight: var(--fw-700);
  white-space: nowrap;
}
.lv-strip-motion-chip .lv-motion-dot {
  width: 7px; height: 7px; border: none; box-shadow: none; margin: 0;
}

/* Hover-scrub preview tile — floats above the track at the cursor. */
.lv-scrub-preview {
  position: absolute; bottom: calc(100% + 8px);
  transform: translateX(-50%);
  z-index: 7; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 6px; border-radius: 10px;
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  box-shadow: 0 8px 24px rgba(6,19,61,.20);
}
.lv-scrub-preview[hidden] { display: none; }
.lv-scrub-thumb {
  position: relative;
  width: 160px; height: 90px; border-radius: 6px; overflow: hidden;
  background: var(--bg-sunken);
  display: flex; align-items: center; justify-content: center;
}
/* Time-only fallback (no motion frame near the cursor): a calm clock glyph. */
.lv-scrub-preview:not(.has-thumb) .lv-scrub-thumb { width: auto; height: auto; padding: 8px 10px; }
.lv-scrub-noimg { color: var(--fg-4); display: inline-flex; }
.lv-scrub-noimg i { width: 20px; height: 20px; }
.lv-scrub-img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* R419 — storyboard sprite tile. The JS sets ALL the sheet geometry inline
   (background-image / -position / -size + the exact tile width & height from the
   sprite-sheet contract) so EXACTLY ONE tile is cropped out; CSS must NOT
   override that sizing. The .lv-scrub-thumb box clips via overflow and centers
   the tile, so a tile smaller/larger than the 160×90 box reads cleanly. */
.lv-scrub-sprite { display: block; flex: none; }
.lv-scrub-sprite[hidden] { display: none; }
.lv-scrub-time {
  font-size: var(--fs-xs); font-weight: var(--fw-700);
  color: var(--fg-2); font-variant-numeric: tabular-nums;
}

/* Compact / touch viewports — larger hit targets, wrap toolbar gracefully */
@media (max-width: 720px) {
  .lv-strip-toolbar { gap: var(--space-2); }
  .lv-strip-spacer { display: none; }
  .lv-strip-zoom button { width: 36px; height: 36px; }
  .lv-strip-track { height: 56px; }
  .lv-scrub-thumb { width: 120px; height: 68px; }
  .lv-motion-mark { width: 18px; transform: translateX(-9px); }
}

.layout-info b { display: block; font-size: var(--fs-small); font-weight: var(--fw-700); color: var(--fg-1); }
.layout-info span { font-size: var(--fs-xs); color: var(--fg-4); display: block; margin-top: 2px; }
.layout-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; }

/* ===== Live View — extras: groups, PTZ, stats, config popover ===== */
.live-page { position: relative; }
.live-shell.no-side { grid-template-columns: 1fr; }

/* AUDIT — the static head bar (.live-player-head-l / .live-player-stats /
   .live-stat-chip) and the always-on stat chips were removed; live metrics now
   live in the overlaid control bar (.live-overlay-stat) and the advanced-stats
   panel. The history-mode chip sits on the dark stage now, so use a solid
   amber for legibility over video. */
.live-chip.history { background: #F59E0B; color: #3A2400; }

/* PTZ overlay — R393: moved to the RIGHT edge of the stage (owner request;
   was bottom-LEFT). Sits above the overlaid control bar so the full-width bar
   doesn't cover the joystick / presets. Shown on demand via the control-bar
   PTZ toggle (window.__livePtzOpen) and only for PTZ-capable cameras. */
.ptz-overlay {
  position: absolute; right: 14px; bottom: 64px;
  display: grid;
  grid-template-areas:
    "joy  zoom"
    "pre  pre";
  gap: var(--space-2); align-items: center;
  justify-items: end;
  z-index: 5;
  animation: cam-menu-pop .14s var(--ease-standard);
}
.ptz-joystick {
  grid-area: joy;
  position: relative;
  width: 110px; height: 110px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(6,21,70,0.65) 0%, rgba(6,21,70,0.40) 60%, rgba(6,21,70,0.30) 100%);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,0.10);
  box-shadow: 0 6px 18px rgba(0,0,0,0.20);
  cursor: grab;
  touch-action: none;
  user-select: none;
}
.ptz-joystick.dragging { cursor: grabbing; }
.ptz-joystick-rings {
  position: absolute; inset: 8px;
  border-radius: 50%;
  border: 1px dashed rgba(255,255,255,0.18);
  pointer-events: none;
}
.ptz-joystick-rings::before {
  content: ""; position: absolute; inset: 18%;
  border-radius: 50%; border: 1px dashed rgba(255,255,255,0.14);
}
.ptz-joystick-axis {
  position: absolute; inset: 8px;
  pointer-events: none;
}
.ptz-joystick-axis::before,
.ptz-joystick-axis::after {
  content: ""; position: absolute;
  background: rgba(255,255,255,0.10);
}
.ptz-joystick-axis::before { left: 0; right: 0; top: 50%; height: 1px; }
.ptz-joystick-axis::after  { top: 0; bottom: 0; left: 50%; width: 1px; }
.ptz-knob {
  position: absolute;
  width: 38px; height: 38px;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #ffffff 0%, #f0f4ff 40%, #cbd5e1 100%);
  box-shadow: 0 4px 10px rgba(0,0,0,0.32), inset 0 -2px 4px rgba(0,0,0,0.10), inset 0 1px 2px rgba(255,255,255,0.6);
  display: grid; place-items: center;
  color: var(--dnuv-blue-dark);
  transition: transform 100ms cubic-bezier(.22,.61,.36,1);
}
.ptz-joystick.dragging .ptz-knob { transition: none; }
.ptz-knob i { width: 16px; height: 16px; }
.ptz-joystick:hover .ptz-knob { box-shadow: 0 4px 14px rgba(0,0,0,0.34), inset 0 -2px 4px rgba(0,0,0,0.10), inset 0 1px 2px rgba(255,255,255,0.7); }
.ptz-readout {
  /* R393 — readout sits ABOVE the joystick now (panel moved to the right edge;
     below would collide with the control-bar scrim and overflow off-stage). */
  position: absolute; left: 50%; bottom: calc(100% + 6px);
  transform: translateX(-50%);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  color: rgba(255,255,255,0.78);
  text-shadow: 0 1px 2px rgba(0,0,0,0.5);
  white-space: nowrap;
}

.ptz-zoom {
  grid-area: zoom;
  display: flex; flex-direction: column; gap: 2px;
  background: rgba(6,21,70,0.55);
  backdrop-filter: blur(8px);
  border-radius: 8px;
  padding: var(--space-1);
}
.ptz-btn {
  background: transparent;
  border: none;
  color: #fff;
  display: grid; place-items: center;
  cursor: pointer;
  border-radius: 6px;
  width: 32px; height: 32px;
  transition: background 100ms;
}
.ptz-btn:hover { background: rgba(255,255,255,.12); }
.ptz-btn i { width: 14px; height: 14px; }
.ptz-presets {
  grid-area: pre;
  display: flex; gap: var(--space-1);
  background: rgba(6,21,70,0.55);
  backdrop-filter: blur(8px);
  border-radius: 8px;
  padding: var(--space-1);
}
.ptz-preset {
  flex: 1;
  height: 26px;
  background: transparent;
  border: none;
  color: #fff;
  border-radius: 5px;
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-700);
  cursor: pointer;
  transition: background 100ms;
}
.ptz-preset:hover { background: rgba(255,255,255,.12); }
.ptz-preset.save { display: grid; place-items: center; }
.ptz-preset.save i { width: 13px; height: 13px; }

/* Stats panel overlay (top-right) */
.live-stats-panel {
  position: absolute; top: 14px; right: 14px;
  width: 280px;
  background: rgba(6,21,70,0.85);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 10px;
  padding: var(--space-3);
  color: #fff;
  z-index: 6;
  font-size: var(--fs-sm);
}
.live-stats-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.live-stats-head b { font-size: var(--fs-sm); font-weight: var(--fw-700); }
.live-stats-close {
  background: transparent; border: none; color: rgba(255,255,255,.7);
  width: 22px; height: 22px;
  display: grid; place-items: center; cursor: pointer; border-radius: 5px;
}
.live-stats-close:hover { background: rgba(255,255,255,.10); color: #fff; }
.live-stats-close i { width: 13px; height: 13px; }
.live-stats-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px 14px;
  font-family: var(--font-mono); font-size: var(--fs-xs);
}
.live-stats-grid > div { display: flex; flex-direction: column; gap: 1px; }
.live-stats-grid > div span { color: rgba(255,255,255,.6); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .04em; }
.live-stats-grid > div b { color: #fff; font-weight: var(--fw-600); }

/* Player config popover — AUDIT: the gear that opens it moved into the
   overlaid control bar at the bottom of the stage, so the popover now anchors
   bottom-right (above the control bar) instead of top-right. z-index sits
   above the control bar (6) but below modals (100). */
.live-cfg-pop {
  position: absolute; right: 24px; bottom: 76px;
  width: 280px;
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: 12px;
  box-shadow: 0 18px 40px rgba(10,31,94,.18), 0 4px 12px rgba(10,31,94,.06);
  padding: var(--space-3);
  z-index: 20;
  animation: cam-menu-pop .14s var(--ease-standard);
}
.live-cfg-section + .live-cfg-section { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border-1); }
.live-cfg-lab {
  display: block;
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing:var(--ls-wide);
  color: var(--fg-4); font-weight: var(--fw-700);
  margin-bottom: 6px;
}
.live-cfg-seg {
  display: flex; gap: 3px;
  padding: 3px;
  background: var(--bg-sunken);
  border-radius: 7px;
}
.live-cfg-seg button {
  flex: 1;
  height: 28px;
  background: transparent; border: none;
  border-radius: 5px;
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-600);
  color: var(--fg-3);
  cursor: pointer;
}
.live-cfg-seg button.active { background: var(--dnuv-blue-50); color: var(--dnuv-blue-dark); box-shadow: 0 1px 2px rgba(10,31,94,.08); }
/* Wave D #49 — read-only access-mode line (the manual LAN/Cloud toggle was
   removed; api.accessMode() decides the transport, this just reports it). */
.live-cfg-readout {
  font-size: var(--fs-sm); font-weight: var(--fw-600); color: var(--fg-2);
  padding: 4px 0;
}
.live-cfg-toggle {
  display: flex; align-items: center; gap: var(--space-2);
  width: 100%; padding: 8px 10px;
  background: var(--bg-sunken);
  border: 1px solid var(--border-1);
  border-radius: 8px;
  font-family: inherit; font-size: var(--fs-small); font-weight: var(--fw-500);
  color: var(--fg-2);
  cursor: pointer;
}
.live-cfg-toggle:hover { background: var(--dnuv-blue-50); border-color: var(--dnuv-blue-200); color: var(--dnuv-blue-dark); }
.live-cfg-toggle.active { background: var(--dnuv-blue-50); border-color: var(--dnuv-blue-light); color: var(--dnuv-blue-dark); }
.live-cfg-toggle i { width: 14px; height: 14px; }
.live-cfg-toggle-state {
  margin-left: auto;
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .04em;
  color: var(--fg-4);
}
.live-cfg-toggle.active .live-cfg-toggle-state { color: var(--dnuv-success); }
/* R393 — read-only resolution/codec/fps line, moved out of the control bar
   into this popover. It reuses .live-overlay-stat for the mono font but needs a
   themed color (the popover is a light surface, not the dark scrim), so this
   rule wins via the more specific selector. */
.live-cfg-pop .live-cfg-stat.live-overlay-stat {
  color: var(--fg-2);
  padding: 0;
}
.live-cfg-hint {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed var(--border-1);
  font-size: var(--fs-xs); color: var(--fg-4);
  text-align: center;
}
.live-cfg-hint kbd {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  background: var(--bg-sunken);
  border: 1px solid var(--border-1);
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 1px 5px;
}

/* Sidebar groups */
.live-cam-groups { padding: var(--space-1) var(--space-2) var(--space-3); overflow-y: auto; flex: 1; }
.live-cam-group + .live-cam-group { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border-1); }
.live-group-head {
  display: flex; align-items: center; gap: 6px;
  width: 100%; padding: 6px 6px;
  background: transparent; border: none;
  font-family: inherit; font-size: var(--fs-xs); font-weight: var(--fw-700); text-transform: uppercase; letter-spacing: .06em;
  color: var(--fg-4);
  cursor: pointer; border-radius: 6px;
  text-align: left;
}
.live-group-head:hover { color: var(--fg-2); background: var(--bg-sunken); }
.live-group-head .chev { width: 12px; height: 12px; transition: transform 160ms; }
.live-group-head .group-name { flex: 1; }
.live-group-head .group-count {
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--fg-4); font-weight: var(--fw-600);
  font-variant-numeric: tabular-nums;
}
.live-cam-group.collapsed .live-group-head .chev { transform: rotate(-90deg); }
.live-cam-group.collapsed .live-group-body { display: none; }
.live-group-body { display: flex; flex-direction: column; gap: 2px; padding: 2px 0; }

/* Offline state extended */
.live-offline-state {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px;
  background: var(--bg-sunken);
  color: var(--fg-3);
  padding: var(--space-8);
}
.live-offline-ico {
  width: 60px; height: 60px;
  border-radius: 50%;
  background: var(--dnuv-danger-bg);
  color: var(--dnuv-danger);
  display: grid; place-items: center;
}
.live-offline-ico i { width: 28px; height: 28px; }
.live-offline-state h2,
.live-offline-state h3 { margin: 0; font-size: var(--fs-body); font-weight: var(--fw-700); color: var(--fg-1); }
.live-offline-state p  { margin: 0; font-size: var(--fs-sm); color: var(--fg-4); text-align: center; max-width: 380px; }
/* R397 v2 (DECLUTTER-1c) — the offline "Gravações disponíveis abaixo — clique
   em um trecho…" recnote pill was removed (LiveView.js no longer emits
   .live-offline-recnote); its now-dead CSS block is dropped here. */
.live-offline-actions { display: flex; gap: var(--space-2); margin-top: 8px; flex-wrap: wrap; justify-content: center; }

/* Wave D #53 — offline as an OVERLAY (not a full-stage dead end). The stage
   <video> stays mounted underneath so the timeline can seek real VOD into it;
   this banner only renders while live is down AND not reviewing a recording,
   and steps aside the moment VOD plays. A dark translucent backdrop (over the
   stage) keeps the control bar (z-index 6) usable on top so the user can click
   the timeline / press rewind to start history playback. Declared AFTER the
   block above so the white-on-dark text overrides win on source order. */
.live-offline-state.live-offline-overlay {
  background: rgba(6,19,61,.78);
  padding: 24px 28px 76px;   /* bottom room so the control bar peeks through */
}
.live-offline-overlay h2,
.live-offline-overlay h3 { color: #fff; }
.live-offline-overlay p { color: rgba(255,255,255,.82); }
.live-offline-overlay .live-offline-ico {
  background: rgba(239,68,68,.22); color: #fff;
}
/* R397 v2 (DECLUTTER-2) — the offline overlay is force-dark (rgba(6,19,61,.78)),
   but .btn-ghost ships dark text (--fg-2) tuned for a LIGHT app surface, so
   "Ver no edge" / "Editar" were invisible-on-navy. Scope a white-on-dark ghost
   here. `.live-offline-overlay .btn-ghost` (0,2,0) beats css/buttons.css and the
   inline `.btn-ghost` (0,1,0) regardless of load order — no !important needed.
   The inherited <i> icon turns #fff with the text. */
.live-offline-overlay .btn-ghost {
  background: rgba(255,255,255,.10);
  color: #fff;
  border-color: rgba(255,255,255,.22);
}
.live-offline-overlay .btn-ghost:hover {
  background: rgba(255,255,255,.18);
  color: #fff;
  border-color: rgba(255,255,255,.32);
}

/* AUDIT — active/talk states for the transport buttons now live on the
   overlaid control bar (.live-overlay-controls .btn-icon.active /
   .talk.active), defined in the Live View section above. */

/* ===== Overview v3 (customizable dashboard) ===== */
.overview { gap: var(--space-4); }
.overview .page-head { padding-bottom: 4px; }
.ov-actions { gap: 6px; }

/* R397 v2 (#7) — header toolbar height parity. The .ov-tf-pick segmented
   control is 36px (--control-h); the dropdown / expand / edit / refresh
   buttons next to it are .btn-sm (28px). Bump the toolbar's small buttons to
   the canonical 36px so the row has ONE height. The `.btn` in the selector
   raises specificity to (0,3,0) so it wins over css/buttons.css
   `.btn-ghost.btn-sm` (0,2,0), which loads AFTER pages.css. */
.ov-actions .btn.btn-sm,
.page-actions .btn.btn-sm { height: var(--control-h, 36px); padding: 0 var(--btn-px, 12px); }
.ov-actions .btn.btn-sm i,
.page-actions .btn.btn-sm i { width: 14px; height: 14px; }

/* Timeframe segmented picker — this is the "30 dias" period control the
   F-BTNH brief names as the canonical-height reference. */
.ov-tf-pick {
  display: inline-flex;
  background: var(--bg-sunken);
  border: 1px solid var(--border-1);
  border-radius: 8px;
  padding: 3px;
  gap: 2px;
  /* W-E/F-BTNH: route the reference control through the canonical token
     (was raw 36px — same value, now single-source). */
  height: var(--control-h, 36px);
  margin-right: 4px;
}
.ov-tf-opt {
  background: transparent; border: none;
  padding: 0 10px; height: 28px;
  border-radius: 5px;
  font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: var(--fw-700);
  color: var(--fg-3);
  cursor: pointer;
  transition: background 100ms, color 100ms;
}
.ov-tf-opt:hover { color: var(--fg-1); }
/* R389 (C0) — active tint uses the theme-aware --dnuv-blue-tint for the bg
   (was the fixed light #dnuv-blue-50). Text repointed to --fg-1 so it stays
   readable on the tint in BOTH themes (dark-blue text on the dark-mode tint
   would have been invisible). */
.ov-tf-opt.active { background: var(--dnuv-blue-tint); color: var(--fg-1); box-shadow: 0 1px 2px rgba(10,31,94,.08); }

/* Fullscreen TV mode */
body.ov-fullscreen .sidebar,
body.ov-fullscreen #appbar-host,
body.ov-fullscreen .help-fab-wrap { display: none !important; }
body.ov-fullscreen .app { grid-template-columns: 1fr !important; }
/* W4-b/U7: fullscreen content padding was 18×22 (both off-scale) →
   --space-5 (20) × --space-6 (24). +2px each axis. */
body.ov-fullscreen .content.fullscreen { padding: var(--space-5) var(--space-6); }
body.ov-fullscreen .content.fullscreen .page-head { padding-top: 0; }

/* Preset picker */
.ov-preset-pick { position: relative; }
.ov-preset-pick .btn { padding-right: 10px; gap: 6px; }
.ov-preset-pick .btn i:first-of-type { color: var(--dnuv-blue-dark); }
.ov-dirty-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--dnuv-blue-dark); margin: 0 2px;
}
.ov-preset-menu {
  position: absolute; top: calc(100% + 6px); right: 0;
  width: 260px;
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: 10px;
  box-shadow: 0 16px 36px rgba(10,31,94,.18), 0 4px 8px rgba(10,31,94,.06);
  padding: var(--space-1);
  display: none;
  flex-direction: column;
  gap: 1px;
  z-index: 50;
  animation: cam-menu-pop .14s var(--ease-standard);
}
.ov-preset-menu.open { display: flex; }
.ov-preset-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  border: none; background: none;
  border-radius: 7px;
  font-family: inherit; font-size: var(--fs-small); font-weight: var(--fw-500);
  color: var(--fg-1); cursor: pointer;
  text-align: left;
  transition: background 120ms;
}
.ov-preset-item:hover { background: var(--bg-sunken); }
/* R389 (C0) — theme-aware active tint + readable text in both themes. */
.ov-preset-item.active { background: var(--dnuv-blue-tint); color: var(--fg-1); font-weight: var(--fw-600); }
.ov-preset-item i { width: 15px; height: 15px; color: var(--fg-3); flex: 0 0 15px; }
.ov-preset-item.active i { color: var(--fg-1); }
.ov-preset-name { flex: 1; }
.ov-preset-mod { font-size: var(--fs-xs); color: var(--fg-4); font-weight: var(--fw-600); text-transform: uppercase; letter-spacing: .04em; }
.ov-preset-check { width: 13px !important; height: 13px !important; color: var(--dnuv-blue-dark) !important; }
.ov-preset-sep { height: 1px; background: var(--border-1); margin: 4px 6px; }

/* Add widget menu (floating) */
.ov-add-menu {
  width: 320px;
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: 10px;
  box-shadow: 0 16px 36px rgba(10,31,94,.18), 0 4px 8px rgba(10,31,94,.06);
  padding: var(--space-1);
  display: flex; flex-direction: column; gap: 1px;
  z-index: 1000;
  max-height: 360px; overflow-y: auto;
  animation: cam-menu-pop .14s var(--ease-standard);
}
.ov-add-menu button {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px;
  border: none; background: none;
  border-radius: 7px;
  font-family: inherit; font-size: var(--fs-small); font-weight: var(--fw-500);
  color: var(--fg-1); cursor: pointer;
  text-align: left;
  transition: background 100ms;
}
.ov-add-menu button:hover { background: var(--bg-sunken); color: var(--dnuv-blue-dark); }
.ov-add-menu button i { width: 15px; height: 15px; color: var(--fg-3); flex: 0 0 15px; }
.ov-add-menu button:hover i { color: var(--dnuv-blue-dark); }
.ov-add-menu button > span:nth-of-type(1) { flex: 1; min-width: 0; }
.ov-add-meta { font-size: var(--fs-xs); color: var(--fg-4); font-variant-numeric: tabular-nums; }

/* R206 — API status banner above the bento grid */
/* R267 (W4-a/U4-P0-8) — Same selector list also covers `.data-banner` (the
   orphan class emitted from Incidents.js) and the per-page error banner
   names (`.audit-api-error / .sites-api-error / .servers-api-error /
   .cam-detail-api-error`). The audit's recommended fix is to unify under
   a single `.api-banner` family. We do that by extending the selector list
   here so existing markup keeps working but receives canonical styling. */
.ov-api-banner,
.data-banner,
.api-banner,
.audit-api-error,
.sites-api-error,
.servers-api-error,
.cam-detail-api-error {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-small);
  font-weight: var(--fw-500);
  margin-bottom: 8px;
}
.ov-api-banner.ov-api-loading,
.data-banner.loading,
.api-banner.is-loading {
  background: var(--dnuv-blue-50);
  color: var(--dnuv-blue-dark);
  border: 1px solid var(--dnuv-blue-100, rgba(60,179,254,.18));
}
.ov-api-banner.ov-api-error,
.data-banner.error,
.api-banner.is-error,
.audit-api-error,
.sites-api-error,
.servers-api-error,
.cam-detail-api-error {
  background: var(--dnuv-danger-bg);
  color: var(--color-danger);
  border: 1px solid var(--color-danger);
}
.ov-api-banner.ov-api-error i,
.data-banner.error i,
.api-banner.is-error i { width: 16px; height: 16px; flex: 0 0 16px; }
.ov-api-banner > span,
.data-banner > span,
.api-banner > span { flex: 1; }
.ov-api-spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid rgba(10,31,94,.18);
  border-top-color: var(--dnuv-blue-dark);
  animation: ov-spin 0.9s linear infinite;
  flex: 0 0 14px;
}
@keyframes ov-spin { to { transform: rotate(360deg); } }

/* Bento grid: 3 columns × auto rows (220px tall) */
.ov-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 220px;
  grid-auto-flow: dense;
  gap: 14px;
  align-items: stretch;
}
@media (max-width: 980px) {
  .ov-grid { grid-template-columns: 1fr; grid-auto-rows: minmax(220px, auto); }
}
@media (min-width: 600px) and (max-width: 980px) {
  /* 2 columns on tablets, but force KPIs to 1 each */
  .ov-grid { grid-template-columns: repeat(2, 1fr); }
}

.ov-widget {
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  display: flex; flex-direction: column;
  overflow: hidden;
  position: relative;
  transition: border-color 120ms, box-shadow 120ms;
}
.ov-widget.c-1 { grid-column: span 1; }
.ov-widget.c-2 { grid-column: span 2; }
.ov-widget.c-3 { grid-column: span 3; }
.ov-widget.r-1 { grid-row: span 1; }
.ov-widget.r-2 { grid-row: span 2; }
.ov-widget.r-3 { grid-row: span 3; }
@media (max-width: 980px) {
  .ov-widget.c-1, .ov-widget.c-2, .ov-widget.c-3 { grid-column: span 1; }
}

.ov-grid.edit .ov-widget {
  border-color: var(--dnuv-blue-light);
  /* R389 (C0) — was a gradient to literal #fff (white in dark mode). Use the
     surface token so the edit-mode tint sits on the themed widget bg. */
  background: linear-gradient(to bottom, rgba(60,179,254,0.03), var(--bg-surface) 30px);
}
.ov-grid.edit .ov-widget.dragging { opacity: 0.45; }
.ov-grid.edit .ov-widget.drop-target { outline: 2px solid var(--dnuv-blue-dark); transform: scale(1.005); }
.ov-grid.edit .ov-widget.resizing { outline: 2px solid var(--dnuv-blue-light); z-index: 5; }

.ov-add-tile {
  background: var(--bg-app);
  border: 1.5px dashed var(--border-2);
  border-radius: var(--radius-card);
  padding: var(--space-8) var(--space-4);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-2);
  font-family: inherit; font-size: var(--fs-small); font-weight: var(--fw-600);
  color: var(--fg-3);
  cursor: pointer; min-height: 140px;
  grid-column: span 1;
  transition: border-color 120ms, background 120ms, color 120ms;
}
.ov-add-tile:hover { border-color: var(--dnuv-blue-light); background: var(--dnuv-blue-50); color: var(--dnuv-blue-dark); }
.ov-add-tile i { width: 20px; height: 20px; }

.ov-widget-edit {
  display: flex; align-items: center; gap: 10px;
  padding: var(--space-2) var(--space-3);
  background: var(--dnuv-blue-50);
  border-bottom: 1px solid var(--dnuv-blue-100);
  font-size: var(--fs-sm);
  cursor: grab;
  user-select: none;
}
.ov-widget-edit:active { cursor: grabbing; }
.ov-widget-handle { color: var(--dnuv-blue-dark); display: inline-grid; place-items: center; }
.ov-widget-handle i { width: 14px; height: 14px; }
.ov-widget-label { flex: 1; display: inline-flex; align-items: center; gap: 6px; color: var(--dnuv-blue-dark); font-weight: var(--fw-700); }
.ov-widget-label i { width: 13px; height: 13px; }
.ov-widget-edit-actions { display: inline-flex; align-items: center; gap: 6px; }
.ov-widget-dims {
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: var(--fw-700);
  color: var(--dnuv-blue-dark); padding: 0 6px;
  background: rgba(0,34,140,.10); border-radius: 4px;
}
.ov-widget-edit-actions button {
  width: 22px; height: 22px;
  background: transparent; border: none;
  border-radius: 5px;
  color: var(--dnuv-blue-dark);
  cursor: pointer;
  display: grid; place-items: center;
  transition: background 100ms;
}
.ov-widget-edit-actions button:hover { background: rgba(0,34,140,.12); }
.ov-widget-edit-actions button i { width: 12px; height: 12px; }
.ov-widget-body { flex: 1; display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.ov-widget-body.edit-pointer-block { pointer-events: none; }
.ov-widget-body.edit-pointer-block * { pointer-events: none; }
/* But allow drag-over events on the body itself */
.ov-grid.edit .ov-widget-body { pointer-events: auto; }
.ov-grid.edit .ov-widget-body > * { pointer-events: none; }

/* Corner resize handle */
.ov-resize-handle {
  position: absolute; right: 4px; bottom: 4px;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  /* R389 (C0) — was rgba(255,255,255,0.92) (white in dark mode). Use the
     elevated surface token for the floating resize handle. */
  background: var(--bg-elevated);
  border: 1px solid var(--dnuv-blue-light);
  border-radius: 6px;
  color: var(--dnuv-blue-dark);
  cursor: nwse-resize;
  z-index: 4;
  user-select: none;
  transition: background 100ms;
}
.ov-resize-handle:hover { background: var(--dnuv-blue-50); }
.ov-resize-handle i { width: 13px; height: 13px; transform: rotate(90deg); }

.ov-widget-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding: 14px 18px 10px;
}
.ov-widget-head h3 { margin: 0; font-size: var(--fs-base); font-weight: var(--fw-700); color: var(--fg-1); }
.ov-widget-head .row-meta { display: block; font-size: var(--fs-sm); margin-top: 2px; }

/* KPI */
/* R267 (W4-a/U4-P0-2) — `.ov-kpi` aligned to the canonical `.kpi-card`
   composite (same surface contract; the legacy class name stays so existing
   Overview.js templates keep rendering). Tabular numerals shared across
   KPI tiles avoid jitter on data-bind updates. */
.ov-kpi { padding: 16px 18px; display: flex; flex-direction: column; gap: 6px; height: 100%; font-variant-numeric: tabular-nums; }
.ov-kpi-head { display: flex; align-items: center; justify-content: space-between; }
.ov-kpi-label { font-size: var(--fs-sm); font-weight: var(--fw-700); color: var(--fg-muted); text-transform: uppercase; letter-spacing: .04em; }
.ov-kpi-delta {
  display: inline-flex; align-items: center; gap: var(--space-1);
  font-size: var(--fs-xs); font-weight: var(--fw-700);
  padding: 3px 7px; border-radius: 999px;
  background: var(--bg-sunken); color: var(--fg-3);
}
.ov-kpi-delta i { width: 11px; height: 11px; }
/* R267 (W4-a/U4-P0-4) — Status-pill drift removed; canonical: --color-*-bg
   token for background, --color-* for text. */
.ov-kpi-delta.up   { background: var(--dnuv-success-bg); color: var(--color-success); }
.ov-kpi-delta.down { background: var(--dnuv-danger-bg);  color: var(--color-danger); }
.ov-kpi-value { font-size: var(--fs-3xl); font-weight: var(--fw-800); color: var(--fg-1); letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.ov-kpi-value small { font-size: var(--fs-base); font-weight: var(--fw-500); color: var(--fg-4); margin-left: 2px; }
.ov-kpi-spark { width: 100%; height: 36px; margin-top: 2px; }
.ov-kpi-meta { font-size: var(--fs-sm); color: var(--fg-4); }

/* Camera tiles inside widget */
.ov-cam-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 10px;
  padding: 10px 14px 14px;
  flex: 1; min-height: 0; overflow-y: auto;
}
.ov-cam-tile {
  background: none; border: none; padding: 0;
  cursor: pointer; text-align: left;
  display: flex; flex-direction: column; gap: 6px;
}
.ov-cam-stage {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: 8px;
  background: linear-gradient(135deg, #cfe0f5, #94b6df);
  overflow: hidden;
  display: grid; place-items: center;
  color: var(--fg-4);
}
.ov-cam-tile.offline .ov-cam-stage { background: var(--bg-sunken); }
.ov-cam-stage i { width: 22px; height: 22px; }
.ov-cam-stage .noise { position: absolute; inset: 0; opacity: 0.35; background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.3) 0%, transparent 50%); }
.ov-cam-stage .grid-overlay { position: absolute; inset: 0; background-image: linear-gradient(rgba(10,31,94,0.10) 1px, transparent 1px), linear-gradient(90deg, rgba(10,31,94,0.10) 1px, transparent 1px); background-size: 32px 32px; }
.ov-cam-rec { position: absolute; top: 8px; right: 8px; width: 8px; height: 8px; border-radius: 50%; background: var(--dnuv-danger); box-shadow: 0 0 0 3px rgba(229,72,77,.20); animation: rec-pulse 1.4s infinite; }
.ov-cam-label {
  position: absolute; bottom: 6px; left: 8px;
  font-size: var(--fs-sm); font-weight: var(--fw-700); color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,.5);
}

/* Activity — Overview "Atividade" widget rows share `.activity-row` base;
 * this rule only overrides grid-template-columns + gap (compact gap variant). */
/* R397 v2 (#39) — the Atividade widget is a fixed-height tile (grid-auto-rows
   220px). overflow:hidden clips the surplus rows (the head shows a "Tudo" link
   to the full Events page and the list is capped to .slice(0,7) in JS), so the
   inner vertical + horizontal scrollbars are gone. */
.ov-activity-list { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
.ov-activity-list .event-row {
  grid-template-columns: 50px 24px 1fr; gap: 10px;
  /* W4-b/U7: was 10×18 → --table-row-py × --table-row-px (12×16). +2/-2 shift. */
  padding: var(--table-row-py) var(--table-row-px);
  /* R397 v2 (#39) — reset the data-dense Events-PAGE 480px floor (inherited
     from the base .event-row) so a narrow dashboard column never forces a
     horizontal scrollbar inside this fixed-height tile. */
  min-width: 0;
}
.ov-activity-list .event-ico { width: 24px; height: 24px; border-radius: 6px; }
.ov-activity-list .event-ico i { width: 12px; height: 12px; }
.ov-activity-list .event-h { font-size: var(--fs-small); }
.ov-activity-list .event-meta { font-size: var(--fs-xs); }

/* Edges in widget */
.ov-edge-list { display: flex; flex-direction: column; padding: 6px 0 14px; flex: 1; min-height: 0; overflow-y: auto; }
.ov-edge-row {
  display: grid;
  grid-template-columns: 12px 1fr 2fr;
  gap: var(--space-3); align-items: center;
  padding: 10px 18px;
  cursor: pointer;
  transition: background 100ms;
}
.ov-edge-row:hover { background: var(--bg-sunken); }
.srv-led { width: 10px; height: 10px; border-radius: 50%; background: var(--fg-4); }
.srv-led.ok  { background: var(--dnuv-success); box-shadow: 0 0 0 3px rgba(15,186,129,.18); }
.srv-led.off { background: var(--dnuv-danger);  box-shadow: 0 0 0 3px rgba(229,72,77,.18); }
.ov-edge-info b { font-size: var(--fs-small); font-weight: var(--fw-600); color: var(--fg-1); display: block; }
.ov-edge-info span { font-size: var(--fs-sm); color: var(--fg-4); }
.ov-edge-metrics { display: flex; gap: 10px; flex-wrap: wrap; }
.ov-edge-metrics .metric { display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-xs); color: var(--fg-3); font-variant-numeric: tabular-nums; }
.ov-edge-metrics .ml { color: var(--fg-4); font-weight: var(--fw-600); min-width: 22px; }
.ov-edge-metrics .mb { width: 60px; height: 4px; background: var(--bg-sunken); border-radius: 3px; overflow: hidden; }
.ov-edge-metrics .mb > span { display: block; height: 100%; background: linear-gradient(90deg, var(--dnuv-blue-dark), var(--dnuv-blue-light)); border-radius: 3px; }
.ov-edge-metrics .mv { color: var(--fg-2); font-weight: var(--fw-600); }
.ov-edge-off { color: var(--dnuv-danger); font-size: var(--fs-sm); display: inline-flex; align-items: center; gap: 6px; }
.ov-edge-off i { width: 13px; height: 13px; }

/* Incidents */
.ov-inc-list { padding: 6px 0 14px; flex: 1; min-height: 0; overflow-y: auto; }
.ov-inc-row {
  display: grid;
  grid-template-columns: auto 1fr 16px;
  gap: var(--space-3); align-items: center;
  padding: 10px 18px;
  cursor: pointer;
  transition: background 100ms;
}
.ov-inc-row:hover { background: var(--bg-sunken); }
.ov-inc-main b { font-size: var(--fs-small); font-weight: var(--fw-600); color: var(--fg-1); display: block; }
.ov-inc-main span { font-size: var(--fs-xs); color: var(--fg-4); }
.ov-inc-arrow { color: var(--fg-4); }

/* Map fill — make the canvas fill the widget body */
.sites-map.ov-map-fill { padding: 0; flex: 1; min-height: 0; display: flex; }
.sites-map.ov-map-fill .sites-map-canvas { aspect-ratio: auto; flex: 1; border: none; border-radius: 0; }

/* Recordings widget */
.ov-rec-list { display: flex; flex-direction: column; padding: 6px 0 14px; flex: 1; min-height: 0; overflow-y: auto; }
.ov-rec-row {
  display: grid; grid-template-columns: 56px 1fr auto;
  gap: var(--space-3); align-items: center;
  padding: 8px 18px;
}
.ov-rec-row:hover { background: var(--bg-sunken); }
.ov-rec-info b { font-size: var(--fs-sm); color: var(--fg-1); display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ov-rec-info span { font-size: var(--fs-xs); color: var(--fg-4); }

/* Onboarding inline */
.ov-onboarding {
  /* R389 (C0) — was a literal #fff→#F5F8FD gradient (white card in dark mode).
     Use surface→sunken tokens so the banner follows the theme. */
  background: linear-gradient(135deg, var(--bg-surface) 0%, var(--bg-sunken) 100%);
  border: 1px solid var(--border-1);
  border-radius: 12px;
  padding: 14px 18px 16px;
}
.ov-onb-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: 8px; }
.ov-onb-head b { font-size: var(--fs-base); color: var(--fg-1); font-weight: var(--fw-700); }
.ov-onb-head span { font-size: var(--fs-sm); color: var(--fg-4); margin-left: 8px; }
.ov-onb-close { background: transparent; border: none; color: var(--fg-4); width: 26px; height: 26px; border-radius: 5px; cursor: pointer; display: grid; place-items: center; }
.ov-onb-close:hover { background: var(--bg-sunken); color: var(--fg-1); }
.ov-onb-close i { width: 14px; height: 14px; }
.ov-onb-bar { height: 4px; background: var(--bg-sunken); border-radius: 3px; overflow: hidden; margin-bottom: 12px; }
.ov-onb-bar > span { display: block; height: 100%; background: linear-gradient(90deg, var(--dnuv-blue-dark), var(--dnuv-blue-light)); border-radius: 3px; }
.ov-onb-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--space-2); }
.ov-onb-step {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 10px 12px;
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: 9px;
  cursor: pointer;
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-600);
  color: var(--fg-2); text-align: left;
  transition: border-color 120ms, color 120ms;
}
.ov-onb-step:hover { border-color: var(--dnuv-blue-light); color: var(--dnuv-blue-dark); }
/* R267 (W4-a/U4-P0-4) — Status-pill drift removed; canonical tokens. */
.ov-onb-step.done { background: var(--dnuv-success-bg); border-color: var(--color-success); color: var(--color-success); }
.ov-onb-ico { width: 22px; height: 22px; border-radius: 6px; background: var(--bg-sunken); display: grid; place-items: center; color: var(--fg-3); flex: 0 0 22px; }
.ov-onb-step.done .ov-onb-ico { background: var(--color-success); color: #fff; }
.ov-onb-ico i { width: 12px; height: 12px; }

/* Edit help banner */
.ov-edit-help {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 10px 14px;
  background: var(--dnuv-blue-50);
  border: 1px solid var(--dnuv-blue-100);
  border-radius: 9px;
  font-size: var(--fs-sm); color: var(--dnuv-blue-dark);
  width: fit-content;
  margin: 0 auto;
}
.ov-edit-help i { width: 13px; height: 13px; }
.ov-edit-help b { font-weight: var(--fw-700); }
.ov-edit-help kbd {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  background: var(--bg-surface);
  border: 1px solid var(--dnuv-blue-200);
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 1px 5px;
  color: var(--dnuv-blue-dark);
}

/* Tip below grid */
.ov-tip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 10px 14px;
  background: var(--bg-sunken);
  border-radius: 9px;
  font-size: var(--fs-sm); color: var(--fg-3);
  width: fit-content;
  margin: 0 auto;
}
.ov-tip i { width: 13px; height: 13px; color: var(--dnuv-blue-dark); }

/* `.bill-usage-*` family canonical definitions live below (single source).
   W3-e (U7 P0) — earlier duplicate compact-form block removed. */

/* "Managed by Stripe" inline footnote on cards */
.bill-stripe-foot {
  display: flex; align-items: center; gap: 6px;
  margin-top: 10px; padding-top: 10px;
  border-top: 1px dashed var(--border-1);
  font-size: var(--fs-sm); color: var(--fg-4);
}
.bill-stripe-foot .stripe-logo {
  font-family: -apple-system, sans-serif;
  font-weight: var(--fw-800); letter-spacing: -.02em;
  color: #635BFF;
}

/* W3-e (U7 P0) — stray `}` orphan deleted (parser-safe but seam marker).
   W3-e (U4 P0) — `.bill-plan-card` / `.bill-invoice-card` rich-variant
   selectors removed: zero JS references, dead 95 LoC. The hero card
   pattern was superseded by the `.bill-plan-tile` grid earlier in this
   file (lines ~469-540). */

/* Usage grid — single canonical block (duplicate at file bottom removed
   in W3-e per U7 P0 dedup). */
.bill-usage-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--space-3);
}
.bill-usage-card {
  background: var(--bg-app); border: 1px solid var(--border-1); border-radius: 10px;
  padding: 12px 14px;
  display: flex; flex-direction: column; gap: 6px;
}
.bill-usage-head { display: flex; justify-content: space-between; align-items: center; }
.bill-usage-lab  { font-size: var(--fs-sm); color: var(--fg-3); font-weight: var(--fw-600); }
.bill-usage-pct  { font-size: var(--fs-xs); font-weight: var(--fw-800); color: var(--fg-2); font-variant-numeric: tabular-nums; }
/* W-E/F-DARK: was #5C3700 (light-only). Canonical warn-fg flips per theme. */
.bill-usage-pct.warn { color: var(--color-warn-fg); }
.bill-usage-pct.high { color: var(--dnuv-danger); }
.bill-usage-val  { font-size: var(--fs-lg); font-weight: var(--fw-700); color: var(--fg-1); }
.bill-usage-val small { font-size: var(--fs-sm); font-weight: var(--fw-500); color: var(--fg-4); margin-left: 2px; }
.bill-usage-bar  { height: 5px; background: rgba(10,31,94,.08); border-radius: 3px; overflow: hidden; }
.bill-usage-bar > span { display: block; height: 100%; background: linear-gradient(90deg, var(--dnuv-blue-dark), var(--dnuv-blue-light)); border-radius: 3px; }
.bill-usage-bar > span.warn { background: linear-gradient(90deg, #B07820, #F59E0B); }
.bill-usage-bar > span.high { background: var(--dnuv-danger); }

/* W12.B14 — Real-usage KPI strip injected at the top of the Uso tab.
   Five tiles in a single row (collapsing to two columns on mobile), each
   showing a single metered KPI sourced from /api/billing/usage/{org_id}. */
.bill-kpi-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--space-3);
}
@media (max-width: 900px) { .bill-kpi-grid { grid-template-columns: repeat(2, 1fr); } }
/* W-E/F-GRID: collapse to ONE column on narrow phones so five metered KPI
   tiles don't cram into two tight columns (was: stayed at 2 cols forever). */
@media (max-width: 540px) { .bill-kpi-grid { grid-template-columns: 1fr; } }
.bill-kpi-tile {
  background: var(--bg-app); border: 1px solid var(--border-1); border-radius: 10px;
  padding: 12px 14px;
  display: flex; flex-direction: column; gap: 4px;
}
.bill-kpi-tile.bill-kpi-total { background: var(--bg-2); }
.bill-kpi-label { font-size: var(--fs-sm); color: var(--fg-3); font-weight: var(--fw-600); }
.bill-kpi-value { font-size: var(--fs-lg); font-weight: var(--fw-700); color: var(--fg-1); font-variant-numeric: tabular-nums; }
.bill-kpi-error { color: var(--dnuv-danger); }

/* AdminCostMetrics page — cross-org cost/revenue admin dashboard.
   Reuses bento-style tiles + a numeric table; sparkline svg styles already
   live in _sparkline.js callsites. */
.ac-header { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.ac-header h1 { font-size: var(--fs-xl); font-weight: var(--fw-700); }
.ac-period { color: var(--fg-3); font-size: var(--fs-sm); }
.ac-kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-3); margin-bottom: 16px; }
@media (max-width: 900px) { .ac-kpis { grid-template-columns: 1fr; } }
.ac-kpi {
  background: var(--bg-app); border: 1px solid var(--border-1); border-radius: 10px;
  padding: 16px 18px; display: flex; flex-direction: column; gap: 4px;
}
.ac-kpi-label { font-size: var(--fs-sm); color: var(--fg-3); font-weight: var(--fw-600); }
.ac-kpi-value { font-size: var(--fs-xl); font-weight: var(--fw-700); color: var(--fg-1); font-variant-numeric: tabular-nums; }
.ac-kpi-hint { font-size: var(--fs-xs); color: var(--fg-4); }
.ac-sparkline-block { margin-bottom: 16px; }
.ac-sparkline { width: 100%; max-width: 320px; }
.ac-org-block table { width: 100%; border-collapse: collapse; }
.ac-org-table th, .ac-org-table td { padding: 8px 12px; border-bottom: 1px solid var(--border-1); text-align: left; }
.ac-org-table th.num, .ac-org-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* W-E/F-DARK: --dnuv-success (#0FBA81) ≈ 2.5:1 on the light table surface
   (fails AA) and stays a fixed mid-green in dark mode. The canonical *-fg
   tints clear AA on light and flip to bright hues in dark — same recipe as
   the sibling .ac-margin-positive/.negative cells. */
.ac-org-table td.num.positive { color: var(--color-success-fg); }
.ac-org-table td.num.negative { color: var(--color-danger-fg); }
.ac-empty, .ac-error, .ac-loading { padding: 24px; text-align: center; color: var(--fg-3); }
.ac-empty-inline { padding: 12px; color: var(--fg-4); }
.ac-footer { margin-top: 16px; display: flex; justify-content: flex-end; }

/* ===========================================================================
   R389 (C0 Foundation) — AdminCostMetrics R319 live markup.
   The block above styles an OLDER .ac-kpi/.ac-org-block set; the current
   AdminCostMetrics.js renders sortable column-header buttons, an expand/drill
   toggle, a margin-band filter group, and margin cells with band colours —
   none of which had any CSS (no focus ring, no band colour, no active cue).
   Focus rings for these controls live in colors_and_type.css (shared group).
   =========================================================================== */

/* Filter row (plan select + margin-band fieldset) */
.ac-filters {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-4);
  padding: var(--space-3) 0 var(--space-4);
}
.ac-filter { display: flex; flex-direction: column; gap: var(--space-1); }
.ac-filter > span {
  font-size: var(--fs-xs); font-weight: var(--fw-600); color: var(--fg-3);
  text-transform: uppercase; letter-spacing: .04em;
}
.ac-margin-filter {
  display: inline-flex; flex-wrap: wrap; gap: var(--space-1);
  border: none; margin: 0; padding: 0;
}

/* Margin-band filter buttons (segmented). Resting = neutral; .active uses a
   blue tint + a non-colour cue (border + weight) so the selection is not
   conveyed by colour alone (WCAG 1.4.1). */
.ac-band-btn {
  display: inline-flex; align-items: center; gap: 6px;
  /* W-E/F-BTNH: alias the canonical small control height (was raw 28px). */
  height: var(--control-h-sm, 28px); padding: 0 10px;
  border: 1px solid var(--border-1); border-radius: var(--radius-pill);
  background: var(--bg-surface); color: var(--fg-2);
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-600);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.ac-band-btn:hover { background: var(--bg-sunken); border-color: var(--border-2); }
.ac-band-btn.active {
  background: var(--dnuv-blue-tint); border-color: var(--dnuv-blue-dark);
  color: var(--fg-1); font-weight: var(--fw-700);
}
/* A leading band-colour dot identifies each band without relying on the
   button label colour. */
.ac-band-btn.band-danger::before,
.ac-band-btn.band-warn::before,
.ac-band-btn.band-positive::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px;
}
.ac-band-btn.band-danger::before   { background: var(--color-danger); }
.ac-band-btn.band-warn::before     { background: var(--color-warn); }
.ac-band-btn.band-positive::before { background: var(--color-success); }

/* Sortable column-header button — fills the <th>, left-aligned, with the
   sort-direction glyph inheriting the header colour. */
.ac-sort-btn {
  display: inline-flex; align-items: center; gap: 6px;
  width: 100%; padding: 0;
  background: none; border: none;
  font: inherit; font-weight: var(--fw-600); color: var(--fg-2);
  cursor: pointer;
  border-radius: var(--radius-xs);
}
.ac-org-table th.num .ac-sort-btn { justify-content: flex-end; }
.ac-sort-btn:hover { color: var(--fg-1); }
.ac-sort-btn i { width: 14px; height: 14px; color: var(--fg-3); flex: 0 0 14px; }

/* Expand/drill toggle (chevron) on each org row. */
.ac-drill-toggle {
  display: inline-grid; place-items: center;
  width: 28px; height: 28px;
  background: none; border: 1px solid transparent; border-radius: var(--radius-xs);
  color: var(--fg-3); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.ac-drill-toggle:hover { background: var(--bg-sunken); color: var(--fg-1); }
.ac-drill-toggle i { width: 16px; height: 16px; }
.ac-org-row.expanded .ac-drill-toggle { color: var(--dnuv-blue-dark); }

/* Margin cell band colours — AA-safe *-fg tints that flip in dark mode
   (7.6–10.3:1 on the table surface). */
.ac-margin-danger   { color: var(--color-danger-fg);  font-weight: var(--fw-700); }
.ac-margin-warn     { color: var(--color-warn-fg);    font-weight: var(--fw-700); }
.ac-margin-positive { color: var(--color-success-fg); font-weight: var(--fw-700); }

/* ---------------------------------------------------------------------------
   R413 — AdminPlatform.js plan-distribution inline MRR bars. Decorative
   (aria-hidden) proportion cue beside the numeric MRR cell; on existing
   tokens (neutral sunken track + blue fill) so it inherits dark-mode flips.
   --------------------------------------------------------------------------- */
.ap-bar-track {
  width: 100%; height: 8px;
  background: var(--bg-sunken);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.ap-bar-fill {
  height: 100%;
  background: var(--dnuv-blue-dark);
  border-radius: var(--radius-pill);
  min-width: 2px;
}

/* ===========================================================================
   R390 (Wave-1 followup) — AdminCostMetrics R319 markup that C0 missed:
   the KPI grid (`.ac-kpi-grid`/`.ac-kpi-card`), the data-fallback banner
   (`.ac-fallback-banner`, currently inline-styled in JS), the per-feature
   stacked bar chart (`.ac-bar-*`), its legend (`.ac-legend-*`), the
   visually-hidden data table (`.ac-feature-table`) and the sparkline frame
   (`.ac-spark`). The existing `.ac-kpi-title/value/hint` rules above are
   reused by `_renderKpiCard`. Bar segment colours come from the categorical
   chart tokens (`--dnuv-amber/violet/teal/rose-500`, set inline in the JS);
   here we own layout + neutral surfaces only. All on existing tokens.
   =========================================================================== */

/* KPI grid + cards (overview tab). Four metric tiles, responsive down to 1
   column on phones. */
.ac-kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.ac-kpi-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-left: 3px solid var(--border-2);
  border-radius: var(--radius-card);
  padding: var(--space-4) var(--space-5);
  display: flex; flex-direction: column; gap: var(--space-1);
  box-shadow: var(--shadow-card);
}
/* `accentClass` from _marginBandClass() tints the leading rail without
   relying on colour alone for meaning (the value + hint carry the data). */
.ac-kpi-card.positive { border-left-color: var(--color-success); }
.ac-kpi-card.warn     { border-left-color: var(--color-warn); }
.ac-kpi-card.danger   { border-left-color: var(--color-danger); }

/* Data-fallback banner ("Modo limitado…"). The JS still sets inline colours;
   this class gives the banner a stable layout hook and a token-based fallback
   so a future de-inline drop is mechanical. */
.ac-fallback-banner {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-4);
  background: var(--color-warn-bg);
  border: 1px solid var(--color-warn);
  border-radius: var(--radius-sm);
  color: var(--color-warn-fg);
  font-size: var(--fs-sm);
}
.ac-fallback-banner i { width: 18px; height: 18px; flex: 0 0 18px; }

/* Sparkline frame (overview 12-month trends) — `_renderSparkSection` wraps the
   SVG in `.ac-spark` (role="img"). Keep it fluid up to a readable width. */
.ac-spark { width: 100%; max-width: 480px; }
.ac-spark svg { display: block; width: 100%; height: auto; }

/* Per-feature stacked bar chart (pure CSS). The JS sizes each column's stack
   (`height:%`) and paints each segment (`height:%; background:<token>`)
   inline; CSS owns the column layout, baseline alignment, track surface and
   the month axis labels. */
.ac-bar-chart {
  display: flex; align-items: flex-end; gap: var(--space-2);
  height: 200px;
  padding: var(--space-3) 0 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.ac-bar-col {
  flex: 1 1 0; min-width: 28px;
  height: 100%;
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-1);
}
/* The stack grows from the baseline; its inline height is a % of the column.
   A sunken track sits behind it so empty/short months still read as bars. */
.ac-bar-stack {
  width: 100%; max-width: 40px;
  margin-top: auto;
  display: flex; flex-direction: column-reverse;
  background: var(--bg-sunken);
  border-radius: var(--radius-xs) var(--radius-xs) 0 0;
  overflow: hidden;
  min-height: 2px;
}
.ac-bar-seg { width: 100%; min-height: 0; }
.ac-bar-seg:first-child { border-radius: var(--radius-xs) var(--radius-xs) 0 0; }
.ac-bar-month {
  font-size: var(--fs-xs); color: var(--fg-4);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Chart legend — swatch + label row, wraps on narrow viewports. */
.ac-bar-legend {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-3);
}
.ac-legend-item {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-sm); color: var(--fg-3);
}
.ac-legend-swatch {
  width: 12px; height: 12px; border-radius: 3px; flex: 0 0 12px;
}

/* Visually-hidden data-table representation of the bar chart. The JS already
   composes `.sr-only` so this is hidden in practice; the rule documents the
   intent and protects the layout if `.sr-only` is ever dropped. */
.ac-feature-table { width: 100%; border-collapse: collapse; }
.ac-feature-table th, .ac-feature-table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-1);
  text-align: left; font-size: var(--fs-sm);
}
.ac-feature-table th.num, .ac-feature-table td.num {
  text-align: right; font-variant-numeric: tabular-nums;
}

/* ===========================================================================
   R414 (admin/NOC portal Phase 2) — AdminPlatform.js "Clientes" support board.
   Reuses the AdminCostMetrics table chrome (`.ac-org-table`, `.ac-sort-btn`,
   `.ac-filters`/`.ac-filter`), the existing badge classes (`.status-pill`,
   `.tag-soft`, `.bill-plan-badge`) and the R413 `.ap-bar-track`/`.ap-bar-fill`
   bars. Adds only: clickable rows, the search input, the pagination footer,
   and the detail-drawer layout. All on existing tokens — no new colours.
   =========================================================================== */

/* Clickable customer rows (role="button", keyboard-activatable). */
.ap-orgs-table tbody tr.ap-org-row { cursor: pointer; }
.ap-orgs-table tbody tr.ap-org-row:hover { background: var(--bg-sunken); }
.ap-orgs-table tbody tr.ap-org-row:focus-visible {
  outline: 2px solid var(--dnuv-blue-dark);
  outline-offset: -2px;
}
.ap-cell-name { font-weight: var(--fw-600); color: var(--fg-1); }
.ap-cell-sub  { font-size: var(--fs-xs); color: var(--fg-4); font-family: var(--font-mono); }
/* Negative-margin cell colour-cue (matches AdminCostMetrics `.negative`). */
.ap-orgs-table td.num.negative { color: var(--color-danger-fg); font-weight: var(--fw-700); }

/* Search box — same field metrics as the kit's other text inputs. */
.ap-search-input {
  width: 100%;
  height: var(--control-h, 36px);
  padding: 0 12px;
  background: var(--bg-surface);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-sm);
  color: var(--fg-1);
  font-family: inherit; font-size: var(--fs-base);
}
.ap-search-input:focus-visible {
  outline: 2px solid var(--dnuv-blue-dark);
  outline-offset: 1px;
  border-color: var(--dnuv-blue-dark);
}

/* Pagination footer — "X–Y de TOTAL" + prev/next. */
.ap-pagination {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-1);
}
.ap-page-info { font-size: var(--fs-sm); color: var(--fg-3); font-variant-numeric: tabular-nums; }
.ap-page-controls { display: inline-flex; gap: var(--space-2); }
.ap-page-controls .btn[disabled],
.ap-page-controls .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

/* Detail drawer body (rendered inside the canonical openModal, size:lg). */
.ap-drawer { display: flex; flex-direction: column; gap: var(--space-4); }
.ap-drawer-head { display: flex; flex-direction: column; gap: var(--space-1); }
.ap-drawer-name { font-size: var(--fs-lg); font-weight: var(--fw-700); color: var(--fg-1); }
.ap-drawer-slug { font-size: var(--fs-sm); color: var(--fg-4); font-family: var(--font-mono); }
.ap-drawer-badges { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-1); }
.ap-drawer-section { display: flex; flex-direction: column; gap: var(--space-2); }
.ap-drawer-section-title {
  margin: 0; font-size: var(--fs-sm); font-weight: var(--fw-700);
  color: var(--fg-2); text-transform: uppercase; letter-spacing: .04em;
}
.ap-drawer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-2) var(--space-4);
}
.ap-drawer-field { display: flex; flex-direction: column; gap: 2px; }
.ap-drawer-field-label { font-size: var(--fs-xs); color: var(--fg-4); }
.ap-drawer-field-value { font-size: var(--fs-base); color: var(--fg-1); font-variant-numeric: tabular-nums; }
.ap-drawer-field-value.ap-neg { color: var(--color-danger-fg); font-weight: var(--fw-700); }
.ap-drawer-prob { font-size: var(--fs-sm); color: var(--fg-4); font-variant-numeric: tabular-nums; }

/* Churn-feature labelled bars inside the drawer. */
.ap-churn-feats { display: flex; flex-direction: column; gap: var(--space-2); }
.ap-churn-feat { display: flex; flex-direction: column; gap: 4px; }
.ap-churn-feat-head {
  display: flex; align-items: baseline; justify-content: space-between;
  font-size: var(--fs-sm); color: var(--fg-2);
}
.ap-churn-feat-val { font-size: var(--fs-xs); color: var(--fg-4); font-variant-numeric: tabular-nums; }

/* ===========================================================================
   R415 (admin/NOC portal Phase 3) — AdminPlatform.js "NOC" health board.
   Reuses the R414 board chrome wholesale (`.ac-org-table`/`.ap-orgs-table`
   rows, `.ac-sort-btn`, `.ac-filters`, `.ap-pagination`, the `.ac-kpi-*`
   strip, the `.tag-soft` severity/status badges, and the `.ap-drawer*` drill
   layout). Adds only: the inline online/offline count cells, the
   "Só com problemas" switch, and the drill-drawer offline-edges /
   problem-cameras lists. All on existing tokens — no new colours.
   =========================================================================== */

/* Inline edges/cameras count cells (✓on / ✗off, live / deg / off). Tabular so
   the segments stay column-aligned down the table. The colour-cue classes are
   AA-safe `*-fg` tints that flip in dark mode (matching the table's other band
   cells); the leading glyphs (✓/✗) carry the meaning too, so it is not
   colour-only. */
.ap-h-counts { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ap-h-ok    { color: var(--color-success-fg); font-weight: var(--fw-600); }
.ap-h-off   { color: var(--color-danger-fg);  font-weight: var(--fw-700); }
.ap-h-deg   { color: var(--color-warn-fg);    font-weight: var(--fw-700); }
.ap-h-muted { color: var(--fg-4); }

/* "Só com problemas" switch — an accessible role="switch" button (the visible
   track/thumb is decorative; aria-checked carries the state). On existing
   tokens: neutral track off, brand-blue on. */
.ap-h-toggle { justify-content: flex-end; }
.ap-switch {
  display: inline-flex; align-items: center; gap: var(--space-2);
  height: var(--control-h, 36px); padding: 0 4px;
  background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-600);
  color: var(--fg-2);
}
.ap-switch:focus-visible { outline: none; box-shadow: var(--ring-focus); border-radius: var(--radius-pill); }
.ap-switch-track {
  position: relative; flex: 0 0 auto;
  width: 36px; height: 20px;
  background: var(--bg-sunken); border: 1px solid var(--border-1);
  border-radius: var(--radius-pill);
  transition: background var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard);
}
.ap-switch-thumb {
  position: absolute; top: 1px; left: 1px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--bg-surface);
  box-shadow: 0 1px 2px rgba(10,31,94,.3);
  transition: transform var(--dur-fast) var(--ease-standard);
}
.ap-switch.on .ap-switch-track { background: var(--dnuv-blue-dark); border-color: var(--dnuv-blue-dark); }
.ap-switch.on .ap-switch-thumb { transform: translateX(16px); background: #fff; }
.ap-switch-text { color: var(--fg-3); }
.ap-switch.on .ap-switch-text { color: var(--dnuv-blue-dark); }

/* Drill-drawer offline-edges / problem-cameras lists. Each row: name (+ sub),
   right-aligned badge + relative "offline há …" meta. */
.ap-health-list {
  display: flex; flex-direction: column;
  border: 1px solid var(--border-1); border-radius: var(--radius-sm);
  overflow: hidden;
}
.ap-health-item {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-1);
}
.ap-health-item:last-child { border-bottom: none; }
.ap-health-item-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ap-health-item-name { font-size: var(--fs-base); color: var(--fg-1); font-weight: var(--fw-600); }
.ap-health-item-sub  { font-size: var(--fs-xs); color: var(--fg-4); }
.ap-health-item-meta {
  display: flex; align-items: center; gap: var(--space-2);
  flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end;
}
.ap-health-item-when { font-size: var(--fs-sm); color: var(--fg-3); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ap-health-none { margin: 0; font-size: var(--fs-sm); color: var(--fg-4); }

/* Drill-drawer empty-state (org cleared up between board load and drill). */
.ap-health-empty {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-2);
  padding: var(--space-6) var(--space-4); text-align: center; color: var(--fg-3);
}
.ap-health-empty-emoji { font-size: var(--fs-3xl); line-height: var(--lh-tight); }
.ap-health-empty p { margin: 0; }

/* R396 (player-rework / A1 declutter) — `.bill-two-col` (two-column payment /
   address grid + its 900px @media collapse) removed: no DOM in any kit JS
   references it (grep-confirmed zero hits). */

/* `.bill-card-brand` + `.bill-card-info` — canonical payment-method
   row child styling (used inside `.bill-method-row` in Billing.js).
   The earlier 48×30 duplicate at the top of the file was removed in
   W3-e (U4 P0-U4-5) — this is now the single source. */
.bill-card-brand {
  width: 44px; height: 28px;
  background: linear-gradient(135deg, #1A1F71, #283D87); color: #fff;
  border-radius: 4px;
  display: grid; place-items: center;
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: var(--fw-800);
}
.bill-card-info { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.bill-card-info b { font-size: var(--fs-base); color: var(--fg-1); font-variant-numeric: tabular-nums; }
.bill-card-info span { font-size: var(--fs-sm); color: var(--fg-4); }
.bill-card-extras { display: flex; gap: 8px; margin-top: 12px; }
.bill-card-extras .btn { flex: 1; justify-content: center; }

/* ===========================================================================
   R258 (W2-c/U9) — Shared skeleton classes for FOMD cleanup.

   Used by Overview, CameraGrid, Events, Incidents, Sites, Servers, Users,
   Audit (and W2-d follow-ups) to render a placeholder layout matching the
   final card / row shape while the first fetch is in flight. Replaces the
   FOMD pattern where seed-mock numbers (15/16 online, INC-1042, Ana Nogueira)
   were rendered before the live data landed.

   Respect `prefers-reduced-motion` — the shimmer animation is disabled and
   the background stays muted instead.
   =========================================================================== */
.skel,
.skel-text,
.skel-kpi-value,
.skel-kpi-label,
.skel-kpi-meta,
.skel-row,
.skel-card,
.skel-line,
.skel-circle,
.skel-thumb {
  display: inline-block;
  background: var(--bg-sunken, #F2F6FB);
  background-image: linear-gradient(
    90deg,
    var(--bg-sunken, #F2F6FB) 0%,
    var(--dnuv-surface, #fff) 50%,
    var(--bg-sunken, #F2F6FB) 100%
  );
  background-size: 200% 100%;
  background-position: 100% 0;
  border-radius: 6px;
  color: transparent;
  animation: dnuv-skel-shimmer 1.4s ease-in-out infinite;
  user-select: none;
}
@keyframes dnuv-skel-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skel, .skel-text, .skel-kpi-value, .skel-kpi-label, .skel-kpi-meta,
  .skel-row, .skel-card, .skel-line, .skel-circle, .skel-thumb {
    animation: none;
    background-image: none;
    background: var(--bg-sunken, #F2F6FB);
  }
}

/* Atom variants */
.skel-line       { display: block; height: 12px; border-radius: 4px; }
.skel-line.lg    { height: 16px; }
.skel-line.xl    { height: 22px; }
.skel-line.sm    { height: 10px; }
.skel-circle     { border-radius: 50%; width: 32px; height: 32px; flex: 0 0 32px; }
.skel-thumb      { display: block; height: 54px; width: 96px; border-radius: 6px; }
.skel-thumb.sm   { width: 42px; height: 28px; }

/* KPI tile skeleton — matches widgetKpi() shape (label, value, meta, spark) */
.skel-kpi {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-4);
  background: var(--dnuv-surface, #fff);
  border: 1px solid var(--border-1, var(--dnuv-border));
  border-radius: 12px;
  min-height: 120px;
}
.skel-kpi-label { width: 60%; height: 12px; }
.skel-kpi-value { width: 40%; height: 26px; margin-top: 4px; }
.skel-kpi-meta  { width: 55%; height: 11px; }

/* Row skeleton — matches the data-table tr shape (avatar/thumb + 2 lines) */
.skel-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 12px 14px;
  border-bottom: 1px solid var(--border-1, var(--dnuv-border));
  background: var(--dnuv-surface, #fff);
}
.skel-row .skel-row-body { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.skel-row .skel-line.title { width: 40%; height: 14px; }
.skel-row .skel-line.sub   { width: 65%; height: 10px; }

/* Card skeleton — matches an empty-state card slot */
.skel-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px;
  background: var(--dnuv-surface, #fff);
  border: 1px solid var(--border-1, var(--dnuv-border));
  border-radius: 12px;
  min-height: 220px;
}

/* "Mock-as-skeleton" wrapper — used when a page must still show its grid
   shape (Overview bento, CameraGrid table) while fetching. We hide the
   underlying mock content from screen readers and overlay a skeleton. */
.skel-host { position: relative; }
.skel-host > .skel-overlay {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px;
  background: var(--dnuv-surface, #fff);
  border-radius: var(--radius-card);
}

/* ===========================================================================
   R267 (W4-a/U4) — Canonical card-system primitives.

   Single source of truth for the 16 card families inventoried in the U4 audit
   (docs/ROADMAP-v2-kit-direct.md §6692-6738). Per-family selectors (.cam-card,
   .rec-card, .role-card, …) compose these primitives so they all agree on
   surface color, border, radius, padding, hover, focus-visible, and active
   intent.

   Class taxonomy:
     .card          — base surface (bg + border + radius). NO padding.
     .card-pad      — default padding (16px / --space-4).
     .card-pad-sm   — tight padding (12px / --space-3).
     .card-pad-lg   — generous padding (24px / --space-6).
     .card-hover    — hover lift contract: transition + transform(-2px) +
                      blue-dark border + elevated shadow. Requires .card.
     .card-active   — current-selection intent: blue-dark border + blue tint.

     .kpi-card      — KPI tile composite (replaces hardcoded `.ov-kpi`-style
                      blocks in Overview KPIs).

   Sweep: the per-family selectors below this block still exist, but each one
   now references the same tokens (--radius-card, --shadow-card,
   --shadow-card-hover, --dnuv-blue-tint) so visual drift is bounded by token
   re-bind only.
   =========================================================================== */
.card {
  background: var(--bg-surface);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
.card-pad    { padding: var(--space-4); }
.card-pad-sm { padding: var(--space-3); }
.card-pad-lg { padding: var(--space-6); }

.card-hover {
  cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.card-hover:hover {
  border-color: var(--dnuv-blue-dark);
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-2px);
}
.card-hover:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  border-color: var(--dnuv-blue-dark);
}
.card-hover:active {
  transform: translateY(0);
}
.card-active {
  border-color: var(--dnuv-blue-dark);
  background: var(--dnuv-blue-tint);
}

/* `<button class="card card-hover">` and `<a class="card card-hover">` reset —
   list-row patterns (Events, Incidents, Recordings, Audit) wrap rows in real
   focusable elements per U4-P0-7 (whole-card click target). */
button.card-hover,
a.card-hover {
  display: block;
  width: 100%;
  text-align: inherit;
  color: inherit;
  font: inherit;
  text-decoration: none;
}
button.card-hover { -webkit-appearance: none; appearance: none; }

/* KPI card composite — header (label + delta) | value | sparkline | meta.
   Used by Overview KPI tiles. Replaces the local `.ov-kpi`/`.ov-kpi-value`/
   `.ov-kpi-delta` declarations with token-driven classes; the legacy
   selectors still resolve (composed below) so existing markup keeps working
   during the migration window. */
.kpi-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--bg-surface);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
.kpi-card-label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-500);
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.kpi-card-value {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-700);
  color: var(--fg-1);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-tight);
}
.kpi-card-delta {
  font-size: var(--fs-sm);
  font-weight: var(--fw-500);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.kpi-card-delta.up   { color: var(--color-success); }
.kpi-card-delta.down { color: var(--color-danger); }
.kpi-card-delta.flat { color: var(--fg-muted); }
.kpi-card-meta {
  font-size: var(--fs-sm);
  color: var(--fg-muted);
}

/* R267 — Per-family token re-bind. Keep selector names as-is so existing JS
   markup (CameraGrid `.cam-card`, Recordings `.rec-card`, Multiview
   `.layout-card`, Users `.role-card`, Settings `.integ-card`, Topbar
   `.ab-quick-card`, Overview `.ov-widget`) keeps rendering, but now each
   family's radius / shadow / active state reads from the same tokens.

   - radius:      var(--radius-card)        (was literal 12 px ×7 families)
   - shadow rest: var(--shadow-card)        (was literal `0 1px 2px rgba(…)`)
   - shadow hov:  var(--shadow-card-hover)  (was literal `0 6px 18px rgba(…)`)
   - hover xform: translateY(-2px)          (canonical lift; was -1px on .cam) */
.cam-card,
.rec-card,
.role-card,
.bill-plan-tile,
.integ-card,
.ab-quick-card,
.ov-widget,
.layout-card {
  border-radius: var(--radius-card);
}

/* Canonical hover lift for clickable card families. The U4-P0-3 audit picked
   -2px after comparing the legacy `.cam` (-1px) against the v2 `.cam-card`
   (no lift). This rule supersedes the per-family `:hover` declarations that
   chose different transform/shadow shapes. */
.cam-card:hover,
.rec-card:hover {
  border-color: var(--dnuv-blue-dark);
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-2px);
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.cam-card:focus-visible,
.rec-card:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  border-color: var(--dnuv-blue-dark);
}
.cam-card:active,
.rec-card:active {
  transform: translateY(0);
}

/* R267 — Active / current-selection state for layout-card (Multiview),
   future event-row.is-selected, audit-row.is-selected (audit P0-U4-7). */
.layout-card.active {
  border-color: var(--dnuv-blue-dark);
  background: var(--dnuv-blue-tint);
}

/* R267 (W4-a/U4-P0-7) — Whole-card click target for list-row patterns.
   When a row is wrapped in `<button class="event-row card-hover">` or
   `<a class="audit-row card-hover">`, we keep the bottom-border separator
   look but add the focus ring + cursor + tint-on-hover so the whole row
   reads as the click target. Inner buttons keep working via
   `event.stopPropagation()` on the action handlers. */
button.event-row,
a.event-row,
button.audit-row,
a.audit-row {
  display: grid;            /* event-row uses grid in pages.css */
  width: 100%;
  text-align: left;
  color: inherit;
  font: inherit;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(10, 31, 94, 0.05);
  cursor: pointer;
}
button.event-row:focus-visible,
a.event-row:focus-visible,
button.audit-row:focus-visible,
a.audit-row:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--dnuv-blue-light);
}

/* R267 (W4-a/U4-P0-7) — `.event-row.is-selected` rule. The Events page
   audit P0-U4-7 found the markup emits `is-selected` for checked rows but
   no CSS targeted it. */
.event-row.is-selected,
.audit-row.is-selected {
  background: var(--dnuv-blue-tint);
}

/* R267 (W4-a/U4-P0-4) — Status-pill colors via semantic tokens.
   The U4-P0-4 audit logged 3 different opacity values on the success
   background (0.10 / 0.12 / 0.14 / 0.16) and 4 different shadow opacities
   on the danger LED ring. Canonical: use --color-success / --color-warn /
   --color-danger for text and color-mix or alpha-channel for backgrounds.
   Per-family selectors below still exist; this block pins the dot/text
   color so cross-family hex drift is removed. */
.cam-state.ok    .dot,
.event-status.ok .dot,
.srv-led.ok,
.pill-ok::before { background: var(--color-success); }

.cam-state.warn  .dot,
.event-status.warn .dot,
.srv-led.warn,
.pill-warn::before { background: var(--color-warn); }

.cam-state.off   .dot,
.event-status.off .dot,
.srv-led.off,
.pill-danger::before { background: var(--color-danger); }

/* R389 (C0) — the cam-state *word* used the bright semantic tones
   (--color-success #0FBA81 ≈ 2.5:1, --color-warn #F59E0B ≈ 2.15:1 on white →
   fail AA). Repoint the text to the AA-safe *-fg tints (8.0:1 / 10.5:1 light;
   bright variants in dark). The colored status dot is unchanged above. */
.cam-state.ok    { color: var(--color-success-fg); }
.cam-state.warn  { color: var(--color-warn-fg); }
.cam-state.off   { color: var(--color-danger-fg); }

/* ==========================================================================
   W4-d / U13 — Mobile responsive breakpoints (8 P0)
   ==========================================================================
   Centralized responsive layer addressing the 8 P0 findings from
   docs/ROADMAP-v2-kit-direct.md § U2-Wave (U13):

     1. Sidebar drawer  — slide-in off-canvas <768px (was: horizontal strip)
     2. Tables          — card-stack <768px on Users / Audit; horizontal
                          scroll affordance for data-dense tables (Events)
     3. Modals          — full-screen <640px (no clipping on iPhone)
     4. Topbar chips    — collapsed via overflow menu <768px
     5. Camera grid     — 1/2/3-col responsive cascade
     6. KPI grid        — 1/2/4-col responsive cascade
     7. Touch targets   — 44x44 minimum on mobile (WCAG 2.5.5)
     8. Viewport meta   — verified (no user-scalable=no per a11y)

   Breakpoints (matches --bp-sm/md/lg/xl from colors_and_type.css):
     sm: 640px / md: 768px / lg: 1024px / xl: 1280px
   ----------------------------------------------------------------------- */

/* ---------- Hamburger menu toggle (visible only <768px) ---------- */
/* W4-d/U13 P0-1: lives in the topbar, gated by --bp-md. Topbar.js
   renders the markup on every kit / index.html topbar host.       */
.topbar-mobile-menu {
  display: none;
  width: 44px; height: 44px;
  align-items: center; justify-content: center;
  border: 1px solid var(--border-1);
  border-radius: 10px;
  background: var(--bg-surface);
  color: var(--fg-2);
  cursor: pointer;
  padding: 0;
  font-family: inherit;
  flex: 0 0 44px;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard);
}
.topbar-mobile-menu:hover {
  background: var(--dnuv-blue-50);
  color: var(--dnuv-blue-dark);
  border-color: var(--dnuv-blue-200);
}
.topbar-mobile-menu i { width: 20px; height: 20px; }

/* ---------- Sidebar backdrop (overlay scrim under the drawer) ----------
   Owned by index.html / kit/index.html — appears once next to <aside>.
   Hidden by default; toggled by the hamburger via .app[data-mobile-nav].
*/
.sidebar-backdrop {
  display: none;
  position: fixed; inset: 0;
  background: rgba(10, 31, 94, .45);
  z-index: 49;
  cursor: pointer;
}

/* ---------- Mobile drawer pattern (<768px) ----------
   Replaces the horizontal-scroll strip approach. The legacy
   `@media (max-width: 960px) { .sidebar { flex-direction:row } }` block
   was removed from both index.html shells in the W4-review (cavecrew) pass
   so this drawer pattern is now the only mobile sidebar treatment. The
   responsive ladder: <768px = drawer (this block), >=768px = desktop.
*/
@media (max-width: 767px) {
  /* App shell goes single-column; sidebar floats above content */
  .app {
    grid-template-columns: 1fr !important;
  }

  /* Sidebar becomes off-canvas, sliding in from the left */
  .sidebar {
    position: fixed !important;
    left: 0; top: 0; bottom: 0;
    width: min(86vw, 320px) !important;
    height: 100vh;
    height: 100dvh;
    flex-direction: column !important;
    overflow-x: hidden !important;
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform 250ms var(--ease-standard);
    z-index: 50;
    box-shadow: 0 24px 60px rgba(10, 31, 94, .25);
  }
  .app[data-mobile-nav="open"] .sidebar {
    transform: translateX(0);
  }
  .app[data-mobile-nav="open"] .sidebar-backdrop {
    display: block;
  }

  /* Show the hamburger button in the topbar */
  .topbar-mobile-menu {
    display: inline-flex;
  }

  /* Appbar grows a 4th column for the hamburger (breadcrumb collapses to
     home+current at this breakpoint via the index.html @media rule, so the
     extra column doesn't crowd the search). Allows the search trigger to
     compress so the actions strip fits a 360px viewport. */
  .appbar {
    grid-template-columns: auto auto minmax(0, 1fr) auto !important;
    padding: 0 var(--space-3) !important;
  }
  /* Search placeholder + ⌘K hint hidden on phone (compression mirrors the
     existing 1100px rule but kicks in earlier for very narrow viewports) */
  .search-trigger .ph,
  .search-trigger .kbd { display: none; }
  .search-trigger {
    width: 44px !important;
    height: 44px;
    padding: 0 !important;
    justify-content: center;
  }

  /* Topbar chips: hide period + quick chips on mobile (W4-d/U13 P0-4)
     The period filter and quick-actions menu spill into the overflow
     menu / user panel below the md breakpoint. */
  .ab-chip.period {
    display: none !important;
  }
  .ab-quick {
    display: none !important;
  }

  /* Modal: full-screen on phone (W4-d/U13 P0-3) */
  .modal-backdrop {
    padding: 0 !important;
  }
  .modal,
  .modal.lg,
  .modal.xl {
    max-width: 100vw !important;
    width: 100vw !important;
    max-height: 100vh;
    max-height: 100dvh;
    height: 100vh;
    height: 100dvh;
    border-radius: 0 !important;
    border-left: none;
    border-right: none;
  }
  .modal-card {
    max-width: 100vw !important;
    max-height: 100vh;
    max-height: 100dvh;
    border-radius: 0 !important;
  }
  /* Modal grid-2 collapses to 1-col on phone */
  .modal-body .grid-2 {
    grid-template-columns: 1fr !important;
  }

  /* Camera grid + KPI grid + canonical generic responsive layouts.
     These rules complement the existing index.html `.kpi-row` /
     `.ov-cam-grid` breakpoints, but use the canonical 768px line. */
  .cam-grid,
  .kpi-grid {
    grid-template-columns: 1fr !important;
  }

  /* Mosaic (Multiview) — every multi-col grid collapses on phone.
     W7-v2/U16 #13 adds m-1p5 / m-1p7 spotlight layouts that also collapse;
     Wave F (#2) adds m-5 (25-slot); m-1 already is 1-col, no rule needed. */
  .mosaic.m-2,
  .mosaic.m-3,
  .mosaic.m-4,
  .mosaic.m-5,
  .mosaic.m-1p5,
  .mosaic.m-1p7 {
    grid-template-columns: 1fr !important;
    grid-template-rows: auto !important;
  }
  .mosaic.m-1p5 > .tile:first-child,
  .mosaic.m-1p7 > .tile:first-child {
    grid-column: auto !important;
    grid-row: auto !important;
    /* Wave F (#3) — undo the desktop hero's row-fill so the collapsed single
       column restores the standard 16/9 tile ratio. */
    aspect-ratio: 16 / 9 !important;
    height: auto !important;
  }

  /* Touch-target minimum (WCAG 2.5.5) — W4-d/U13 P0-7
     Every interactive surface gets at least 44x44 on mobile. The
     scoped overrides handle the worst offenders in the U13 audit. */
  .row-kebab,
  .cam-card-menu-btn,
  .ptz-btn,
  .ptz-preset,
  .ov-onb-close,
  .ev-tab,
  .tab-btn {
    min-width: 44px;
    min-height: 44px;
  }
  .btn-icon,
  .ab-icon,
  .topbar-mobile-menu,
  .help-fab {
    min-width: 44px;
    min-height: 44px;
  }

  /* Reduce side padding so content uses the limited viewport */
  .page-shell,
  .page-head {
    padding-left: var(--space-3);
    padding-right: var(--space-3);
  }

  /* W10/U13 — Page canonical wrapper reduces its outer pad on mobile from
     `--page-padding-x` (32px) → `--space-3` (12px) so cards fill the
     narrow viewport. Vertical pad stays at `--page-padding-y` to keep
     content clear of the topbar / safe-area. */
  .content {
    padding-left: var(--space-3) !important;
    padding-right: var(--space-3) !important;
  }
}

/* W10/U13 — Tablet (sm..md, ≥640 < 1024 px) gets `--space-4` (16px) outer
   padding — wider than mobile, snug-er than desktop. Desktop (≥1024 px)
   keeps the full `--page-padding-x` (32px) default. */
@media (min-width: 640px) and (max-width: 1023px) {
  .content {
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
}

/* ---------- Camera grid + KPI grid (mobile-first cascade) ----------
   Generic .cam-grid / .kpi-grid classes any new kit page can use.
   Existing kit pages use .ov-cam-grid / .kpi-row — those keep their
   own rules; the new classes give future code a single canonical path.
*/
.cam-grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}
.kpi-grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}

/* Tablet — pair-up */
@media (min-width: 768px) {
  .cam-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .kpi-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Desktop — full strip */
@media (min-width: 1024px) {
  .cam-grid {
    grid-template-columns: repeat(3, 1fr);
  }
  .kpi-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* ---------- Tables: card-stack <768px (Users + Audit) ----------
   W4-d/U13 P0-2 (Option A): collapsible row → label/value stack on
   primary admin pages. Wrapping <table> in `.data-table-stack` opts
   in to the pattern; the cell's data-label attribute is the header.

   Markup contract:
     <div class="data-table-wrap data-table-stack">
       <table class="data">
         <thead>...</thead>
         <tbody>
           <tr>
             <td data-label="Nome">…</td>
             <td data-label="Função">…</td>
           </tr>
         </tbody>
       </table>
     </div>
*/
@media (max-width: 767px) {
  /* Generic table wrap — horizontal scroll affordance (Option B) */
  .data-table-wrap {
    overflow-x: auto !important;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }

  /* Card-stack opt-in (Option A) for Users/Audit-style admin pages */
  .data-table-stack table.data {
    display: block;
  }
  .data-table-stack table.data thead {
    /* Hide visually but stay accessible to AT */
    position: absolute;
    width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
  }
  .data-table-stack table.data tbody,
  .data-table-stack table.data tr {
    display: block;
  }
  .data-table-stack table.data tr {
    border: 1px solid var(--border-1);
    border-radius: 10px;
    padding: var(--space-3);
    margin-bottom: var(--space-3);
    background: var(--bg-surface);
  }
  .data-table-stack table.data td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) 0;
    border: none;
    border-bottom: 1px dashed var(--border-1);
  }
  .data-table-stack table.data td:last-child {
    border-bottom: none;
  }
  .data-table-stack table.data td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: var(--fs-xs);
    font-weight: var(--fw-700);
    color: var(--fg-3);
    text-transform: uppercase;
    letter-spacing: var(--ls-wide);
  }
  /* When no data-label is set, hide the pseudo so the cell renders flat */
  .data-table-stack table.data td:not([data-label])::before {
    content: none;
  }
}

/* ---------- prefers-reduced-motion — drawer slide ----------
   Sidebar transform animation honours the OS-level preference. */
@media (prefers-reduced-motion: reduce) {
  .sidebar,
  .sidebar-backdrop,
  .topbar-mobile-menu {
    transition: none !important;
  }
}

/* ---------- W7-review/P1-19 — coarse-pointer (touch) hit targets ----------
   WCAG 2.5.5 Target Size (AAA) and the BR-government a11y guidance both ask
   for 44×44 CSS-px hit targets on touch surfaces. The overlaid player controls
   live at 34×34 to keep the chrome dense, but on phones / tablets that's a
   pinch-tap target. Bump .live-overlay-controls .btn-icon and the clip toolbar
   action buttons up to 44×44 when (pointer: coarse) — preserves desktop
   density and meets the AAA bar on touch without a media-width breakpoint
   guess. */
@media (pointer: coarse) {
  .live-overlay-controls .btn-icon {
    min-width: 44px;
    min-height: 44px;
  }
  /* R396 — keep the white glyphs at/above the desktop size on touch (the bigger
     hit target shouldn't shrink the icon). */
  .live-overlay-controls .btn-icon i {
    width: 22px;
    height: 22px;
  }
  .live-overlay-controls .lv-action-playpause i {
    width: 28px;
    height: 28px;
  }
  .lv-clip-action-set-start,
  .lv-clip-action-set-end,
  .lv-clip-action-clear,
  .lv-clip-action-export {
    min-height: 44px;
    min-width: 44px;
    padding-inline: 12px;
  }
}

/* ===========================================================
   R391 reconcile — CSS for features added in the test-fails
   wave whose elements ship with no inline styling. Token-only,
   theme-aware. (The Billing metered card styles itself inline.)
   =========================================================== */

/* LiveView — real volume slider grouped with the audio controls.
   R396 — over-video styling to match the white-hollow controls: a slim white
   track + white thumb (YouTube-like). The slider expands on hover/focus of the
   volume cluster so the resting bar stays compact, like the reference players. */
.lv-volume { display: inline-flex; align-items: center; gap: 4px; }
/* Wave D #55 — enlarge the volume slider HIT-AREA. The visible track stays a
   thin 4px line, but an invisible padded wrapper (.lv-volume-hit) gives a much
   larger pointer/hover/drag target around it (full control-bar height, generous
   horizontal padding) so the slider is easy to grab without a fat visible bar.
   The wrapper itself participates in :hover so the slider reveals when the
   pointer is anywhere in the comfortable target, not only on the 4px line. */
.lv-volume-hit {
  display: inline-flex; align-items: center;
  height: 36px;            /* match the control-bar button height */
  padding: 0 6px;          /* widen the grab zone around the thin track */
  cursor: pointer;
}
.lv-volume-slider {
  width: 0; opacity: 0; height: 4px; margin: 0;
  accent-color: #fff;
  cursor: pointer;
  transition: width .2s var(--ease-standard), opacity .2s var(--ease-standard);
}
/* Reveal on hover/focus of the audio cluster OR the enlarged hit-area. */
.lv-volume:hover .lv-volume-slider,
.lv-volume:focus-within .lv-volume-slider,
.lv-volume-hit:hover .lv-volume-slider { width: 80px; opacity: 1; }
.lv-volume-slider:focus-visible { outline: var(--ring-focus); outline-offset: 2px; }
/* WebKit track/thumb so the white styling lands consistently over video. */
.lv-volume-slider::-webkit-slider-runnable-track { height: 4px; border-radius: 999px; background: rgba(255,255,255,.35); }
.lv-volume-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 12px; height: 12px; margin-top: -4px;
  border-radius: 50%; background: #fff;
  box-shadow: 0 1px 3px rgba(6,19,61,.5);
}
.lv-volume-slider::-moz-range-track { height: 4px; border-radius: 999px; background: rgba(255,255,255,.35); }
.lv-volume-slider::-moz-range-thumb {
  width: 12px; height: 12px; border: none;
  border-radius: 50%; background: #fff;
  box-shadow: 0 1px 3px rgba(6,19,61,.5);
}

/* Storage — quota / allocation summary list (Quota de retenção card) */
.storage-quota-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-1);
}
.storage-quota-item {
  display: flex; justify-content: space-between; gap: var(--space-3);
  font-size: var(--fs-sm); color: var(--fg-2);
}
.storage-quota-item b { color: var(--fg-1); font-weight: var(--fw-600); font-variant-numeric: tabular-nums; }

/* ===========================================================
   R395 — global <fieldset>/<legend> grouping reset
   ===========================================================
   The R389 (C17) + onboarding a11y work wrapped option groups in
   semantic <fieldset>+<legend> so screen readers announce the group
   (e.g. "Modo", "Recursos", "Densidade", "Acessibilidade", "Tema",
   the digest radiogroup, the token "Escopos"/"Eventos" modals…).
   The browser DEFAULT <fieldset> ships a heavy inset 3D border + a
   notch cut into it for the <legend>, which the owner sees as an
   ugly "caixa quadrada" around every grouped area.

   This rule kills ONLY the visual box, app-wide — the semantic
   <fieldset>/<legend> (and any role=radiogroup / aria-labelledby on
   them) stays intact, so the a11y grouping the C17 tests assert is
   preserved. `min-width: 0` is the known fieldset flex/grid
   bug-guard (default `min-inline-size: min-content` otherwise blows
   out grid/flex tracks). Loaded after the inline Settings <style>
   block in kit/index.html so it wins the cascade without !important.

   Components that need a different layout (`.onb-confirm-row`,
   `.ac-margin-filter`) already set their own border:0 + display and
   keep winning on the properties they declare — element specificity
   here is intentionally the lowest.

   The GLOBAL legend rule below is STRUCTURAL ONLY (un-notch: take the
   legend out of the border break and make it a normal block) — it does
   NOT impose typography, so component legends with their own type
   (e.g. onboarding's `.onb-q` question) keep their look. The kit
   small-caps eyebrow group-label typography (same recipe as
   `.nav-section` / `.settings-rail-group-label`) is applied in a
   SCOPED rule further down, only to Settings group legends + the
   `.field-group` modals — token-only, theme-safe (light/dark/system). */
fieldset {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}
/* `:not(.sr-only)` so visually-hidden group names (e.g. CameraDetail
   "Detecções de IA", the margin-band filter "Faixa de margem") keep
   the pure `.sr-only` clip-rect — its negative margin must not be
   clobbered here. Structural un-notch only. */
fieldset > legend:not(.sr-only) {
  display: block;
  float: none;
  width: auto;
  padding: 0;
}
/* Eyebrow group-label typography — scoped to the contexts that want a
   clean small-caps section label (Settings option groups + the modal
   `.field-group`s). Onboarding/admin/camera legends are intentionally
   excluded so their own typography survives. */
.set-card fieldset > legend:not(.sr-only),
fieldset.field-group > legend:not(.sr-only) {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-700);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  color: var(--fg-4);
}

/* ----- R395 — Settings fieldset layout (Problem 2: dead white void) -----
   The Settings option-group fieldsets reuse `.set-row` (a 2-col
   `180px 1fr` grid) and `.set-field` (a flex column). A <legend> is
   special-cased OUT of grid flow by the browser, so a
   `<fieldset class="set-row">` had only ONE in-flow grid child (the
   `.set-row-control`). That single child landed in the 180px column
   and left the entire `1fr` column empty — the "huge empty white
   area" the owner red-boxed inside "Recursos" (and the same in
   "Tema" / "Acessibilidade"). Collapsing these fieldsets to a block
   stack lets the legend (now a top label) sit above a full-width
   control. `.split-2` cells that ARE fieldsets (Densidade / Modo)
   keep their place in the PARENT grid — only their own internal grid
   is neutralised. */
fieldset.set-row {
  display: block;
}
/* Legend already provides the group label on top; the old left-column
   `.set-row-label` padding-top no longer applies (it was a grid-cell
   nudge). The eyebrow `margin-bottom` from the global rule spaces it. */
fieldset.set-row > .set-row-control {
  width: 100%;
}
/* Inside flex containers (`.set-field`, `.set-row-control`) the legend's
   bottom margin would stack on top of the container `gap` → kill it and
   let the gap own the rhythm. */
fieldset.set-field > legend:not(.sr-only),
.set-row-control > legend:not(.sr-only),
.notif-digest-options > legend:not(.sr-only) {
  margin-bottom: 0;
}

/* Feature/toggle lists that used to span full width with a single
   column (label far-left, switch far-right, acres of space between)
   now flow in a responsive 2-column grid so they FILL the card width
   and read as intentional. Falls back to 1 column under ~520px. Each
   `.toggle-row` keeps its dashed divider; we drop the last-child
   border-removal exception here because every cell can be a visual
   "last" in its column. */
.toggle-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0 var(--space-5);
  align-items: start;
}
.toggle-grid .toggle-row { border-bottom: 1px dashed var(--border-1); }

/* ----- R395 — `.field-group`: opt-in subtle card grouping -----
   For the few fieldsets that genuinely benefit from a visible
   container (the modal "Escopos" / "Eventos" checkbox lists, where a
   light boundary helps separate a long multi-select group from the
   rest of the form). Uses the same token border + radius family as
   `.set-card` — NOT the raw browser fieldset border — and the legend
   is the clean eyebrow top-label from the global rule (no notch). */
fieldset.field-group {
  border: 1px solid var(--border-1);
  border-radius: 10px;
  padding: var(--space-3);
  margin-bottom: var(--space-3);
}
/* Inline-block so a trailing `.ctx-help` (?) button stays on the same
   line as the group label; the block-level checkbox container that
   follows drops to the next line on its own. */
fieldset.field-group > legend:not(.sr-only) {
  display: inline-block;
  vertical-align: middle;
  margin-bottom: var(--space-2);
}

/* =========================================================
   R435 (B5) — Round-3 cross-screen consistency primitives
   ---------------------------------------------------------
   These are the shared design-system rules the Round-3 audit asked B5 to
   provide so the per-screen agents (A1–A22) can converge on ONE component
   instead of re-inventing each. Every selector below is NEW (additive) —
   nothing here redefines an existing rule. Markup hooks already exist where
   noted (e.g. OperatorSupport emits .os-grid/.os-field today).
   ========================================================= */

/* ----- R3 339 (B5) — OperatorSupport `.os-grid` / `.os-field` -----
   `.os-grid`/`.os-field` were emitted by OperatorSupport.js (the org-detail
   drawer's Plano/Uso/Assinatura/Branding/Storage sections) but had NO CSS
   anywhere, so the key/value pairs collapsed into a dense flat stack. Define
   the responsive grid + label/value column the markup expects. */
.os-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px 24px;
}
.os-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* ----- R3 616 (B5) — canonical card header (NO divider) -----
   Owner brand decision (Decisões-de-marca #5): card headers use spacing only,
   NO divider rule; the leading icon is optional by rule. Card headers had two
   anatomies app-wide (header + divider rule à la Settings vs title-only à la
   Analytics/Billing/Status). `.card-head` is the canonical title-only header
   the per-screen agents apply to every titled panel. It deliberately carries
   NO border-bottom (the divider is the outlier being retired). The legacy
   `.panel-head` rule that ships a `border-bottom` lives in index.html's inline
   <style> (B1's file) — flagged to B1 to drop that divider; this canonical
   class is the divider-free target for everything else. */
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  /* spacing only — no border-bottom (R3 616 canonical = no divider rule) */
}
.card-head .card-head-l {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.card-head h2,
.card-head h3 {
  font-size: var(--fs-lg);
  font-weight: var(--fw-700);
  color: var(--fg-1);
  line-height: var(--lh-tight);
  margin: 0;
}
/* Optional leading icon — present or absent by rule, never per-screen drift. */
.card-head .card-head-ico {
  display: inline-grid;
  place-items: center;
  color: var(--fg-3);
}
.card-head .card-head-ico i { width: 16px; height: 16px; }
.card-head .card-head-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* ----- R3 607 (B5) — ONE Select primitive -----
   Owner brand decision (Decisões-de-marca #3): a single custom Select
   (height == input, consistent padding, ONE chevron-down glyph, same
   radius/border). There was no single Select primitive — Cargo, the phone
   country picker, "Por página", the date-picker and the still-native invite
   modal <select> all differed in height/padding/chevron (some chevron-down,
   some double up/down). `.ds-select` is the canonical re-skin target; the
   per-screen agents (A2 invite modal, A7 Cargo, A8 "Por página", etc.) apply
   it. Height is the canonical control height (== `.input`), one chevron-down
   rendered as a CSS background (single glyph, single size), same radius/border
   tokens as `.input`. Works on a native <select class="ds-select"> (chrome
   hidden via appearance:none) so it's a drop-in for the native selects the
   audit flagged, AND on a custom button[role=combobox] trigger. */
.ds-select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  display: inline-flex;
  align-items: center;
  width: 100%;
  height: var(--control-h, 36px);
  padding: 0 calc(var(--space-3) + 18px) 0 var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-input, 8px);
  background-color: var(--bg-surface);
  color: var(--fg-1);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: var(--fw-400);
  line-height: var(--lh-normal);
  cursor: pointer;
  outline: none;
  /* Single chevron-down glyph (one size), right-aligned. Drawn with the same
     pure-CSS double-gradient triangle the existing `select.input` primitive
     uses (index.html) so the chevron is byte-for-byte the same shape and the
     colour reads from the --fg-3 token (theme-aware, no hardcoded hex, no OS
     arrow, no double-chevron). */
  background-image:
    linear-gradient(45deg,  transparent 50%, var(--fg-3) 50%),
    linear-gradient(135deg, var(--fg-3) 50%, transparent 50%);
  background-position: calc(100% - 16px) 50%, calc(100% - 11px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  transition: border-color var(--dur-fast, 120ms) var(--ease-standard),
              box-shadow   var(--dur-fast, 120ms) var(--ease-standard);
}
.ds-select:hover { border-color: var(--dnuv-blue-200, var(--dnuv-blue-light)); }
.ds-select:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  border-color: var(--dnuv-blue-dark);
}
.ds-select:disabled,
.ds-select[aria-disabled="true"] {
  opacity: .5;
  cursor: not-allowed;
}
/* Small variant — aligns to the compact toolbar control height (e.g. the
   "Por página" picker sitting in a table footer). */
.ds-select.ds-select-sm {
  height: var(--control-h-sm, 28px);
  font-size: var(--fs-sm);
  padding-right: calc(var(--space-3) + 14px);
}

/* ----- R3 624 (B5) — status-dot color scale -----
   "offline" was red in Câmeras/Servidores but amber in Locais; no consistent
   green/amber/red. Lock the scale (tokens in colors_and_type.css) so Servers
   (A5) and Sites (A6) apply the SAME semantics: green=online,
   amber=partial/degraded, red=offline/down. `.status-dot` is the dot;
   `.status-dot--{state}` colours it. Pairs with a sibling text label
   (`dot + label`, the dominant row-status pattern per R3 623). */
.status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: 0 0 8px;
  background: var(--fg-4);
}
.status-dot--online   { background: var(--status-online); }
.status-dot--degraded { background: var(--status-degraded); }
.status-dot--offline  { background: var(--status-offline); }

/* ----- R3 622 (B5) — count-tab pill (active brand-blue / inactive grey) -----
   List count-tabs drifted: inactive count was raw text (Câmeras/Equipe) vs
   filled-grey pill (Gravações/Servers) vs absent (Incidents); active navy vs
   blue. The existing `.page-tab .tab-count` (above) already renders the
   neutral pill; this canonical `.count-pill` is the standalone version for
   tab strips NOT built on `.page-tab`, so per-screen agents get the same
   shape/colour: active = brand-blue pill, inactive = neutral-grey pill, and
   the count is ALWAYS rendered (0 included). Same radius/shape; only the
   fill changes by active state. */
.count-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: rgba(10,31,94,.08);
  color: var(--fg-3);
  font-size: var(--fs-xs);
  font-weight: var(--fw-700);
  letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}
.is-active > .count-pill,
.count-pill.is-active {
  background: var(--btn-primary);
  color: var(--btn-primary-fg);
}

/* ----- R3 612 (B5) — canonical UPPERCASE tinted table header band -----
   Column-header casing drifted (Eventos sentence-case; operator-cameras
   Title-Case bold with no band; the rest UPPERCASE spaced tinted). The
   canonical band is UPPERCASE + letter-spaced + a subtle tinted background.
   `.data` table headers already follow this in most screens; `.thead-band`
   is the explicit class A3 (Events) and A21 (operator modal) adopt to convert
   their headers without re-deriving the look. */
.thead-band th,
th.thead-band {
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  font-size: var(--fs-xs);
  font-weight: var(--fw-700);
  color: var(--fg-3);
  background: var(--bg-sunken);
}

/* ----- R3 594/595 (B5) — page-level retry/empty-state recovery hooks -----
   Companion to buttons.css's .btn-retry / .btn-refresh. A page-level empty or
   error block's PRIMARY recovery is royal-blue (.btn.btn-primary.btn-retry);
   when it must span the card it opts into .btn-block (never an arbitrary
   full-width). `.empty-actions` (already used by .empty-state in index.html)
   centres the action row; this rule just guarantees the gap is consistent
   wherever an empty/error block is rendered outside the index.html
   `.empty-state` scope. */
.ds-empty-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
