/*
 * Ad Information Dashboard styles
 *
 * Uses the shared theme custom properties (css/theme-variables.css) so light /
 * dark mode work automatically. Scoped to the .ai-* class family to avoid
 * colliding with other dashboards' styles.
 */

/* Restore normal page scrolling. The shared css/dashboard.css sets
   `body { overflow: hidden; height: 100vh; }` for the two-column nav dashboards
   (which scroll inside a fixed-height #dashboard flex container). This page is a
   single full-width column with no #dashboard wrapper, so that rule trapped any
   content below the fold (no scrollbar, dead mouse wheel). Scope the override to
   this page only via the body class so the two-column dashboards are unaffected. */
body.ai-scroll-page {
    overflow: auto;
    height: auto;
    min-height: 100vh;
}

.ai-page-header {
    padding: 0 20px;
    margin-top: 8px;
}

.ai-page-header h1 {
    margin: 0;
}

.ai-subtitle {
    margin: 4px 0 0;
    color: var(--theme-text-tertiary);
    font-size: 0.85rem;
    max-width: 70ch;
}

/* ---- server-context banner ---- */
/* Prominent, always-visible bar naming WHERE the metrics come from. Visually
   distinct (not a tiny subtitle) so the active server is unmistakable. */

.ai-server-banner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin: 12px 20px 0;
    padding: 10px 16px;
    border: 1px solid var(--theme-border-primary);
    border-left-width: 4px;
    border-radius: 6px;
    background-color: var(--theme-bg-secondary);
    font-size: 0.9rem;
}

.ai-server-banner.ai-banner-live {
    border-left-color: var(--status-green);
}

.ai-server-banner.ai-banner-warn {
    border-left-color: var(--status-yellow);
}

/* Caution banner variant (red left border). AdInfo no longer applies this for
   Production (WG.6); rule retained as harmless shared CSS if other hosts use it. */
.ai-server-banner.ai-banner-caution {
    border-left-color: var(--status-red);
    background-color: rgba(220, 53, 69, 0.08);
}

.ai-banner-mode {
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.72rem;
    padding: 2px 8px;
    border-radius: 10px;
    background-color: var(--theme-bg-tertiary);
    color: var(--theme-text-secondary);
}

.ai-banner-env {
    font-weight: 700;
    color: var(--theme-text-primary);
}

.ai-banner-host {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8rem;
    color: var(--theme-text-tertiary);
}

.ai-banner-text {
    color: var(--theme-text-secondary);
}

/* Degraded-read strip — a read that FAILED while the rest of the view stayed usable.
   Amber, not red: the page is still showing real data, and an alarming colour for a
   partially-degraded view trains operators to ignore the strip that also carries a
   genuinely blank one. `hidden` on the element wins over the flex display. */
.ai-degraded-notice {
    margin: 8px 20px 0;
    padding: 10px 16px;
    border: 1px solid var(--theme-border-primary);
    border-left: 4px solid var(--status-yellow);
    border-radius: 6px;
    background-color: var(--theme-bg-secondary);
    font-size: 0.85rem;
    color: var(--theme-text-secondary);
}

.ai-degraded-notice[hidden] {
    display: none;
}

.ai-degraded-title {
    display: block;
    font-weight: 700;
    color: var(--theme-text-primary);
    margin-bottom: 4px;
}

.ai-degraded-list {
    margin: 0;
    padding-left: 20px;
    line-height: 1.45;
}

.ai-degraded-list li + li {
    margin-top: 4px;
}

/* The VPN access precondition, stated ONCE per strip rather than once per failed read
   — three reads failing the same way must not print the same paragraph three times. */
.ai-degraded-vpn {
    margin: 8px 0 0;
    padding-top: 6px;
    border-top: 1px solid var(--theme-border-primary);
    color: var(--theme-text-secondary);
}

/* A best-effort per-row value whose read FAILED, as opposed to one that legitimately
   has nothing to show (which stays the muted em dash). The two must never look alike. */
.ai-unknown {
    color: var(--status-yellow);
    font-weight: 700;
    cursor: help;
}

.ai-banner-note {
    color: var(--status-red);
    font-weight: 600;
    flex-basis: 100%;
}

/* "View Test7 ad activity" affordance — steers the user when no live base
   resolves, or from the reactive unavailable state after a failed ad fetch.
   Right-aligned in the banner row. */
.ai-banner-switch {
    margin-left: auto;
    background-color: var(--theme-bg-tertiary);
    color: var(--theme-link-color);
    border: 1px solid var(--theme-link-color);
    border-radius: 4px;
    padding: 5px 12px;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
}

.ai-banner-switch:hover {
    background-color: var(--theme-bg-selected);
}

/* Detection-method caption on the Ad Detection tab (text set from loaded
   detection_method distribution; WG.6). Muted/advisory. */
.ai-detection-caveat {
    margin: 0 0 16px;
    padding: 8px 12px;
    border-left: 3px solid var(--status-yellow);
    border-radius: 4px;
    background-color: var(--theme-bg-secondary);
    color: var(--theme-text-secondary);
    font-size: 0.82rem;
}

/* ---- toolbar ---- */

.ai-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 18px;
    padding: 12px 20px;
    margin: 12px 0;
    background-color: var(--theme-bg-secondary);
    border-top: 1px solid var(--theme-border-primary);
    border-bottom: 1px solid var(--theme-border-primary);
}

.ai-toolbar-group {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* An AUTHOR `display` rule beats the user-agent's `[hidden] { display: none }`, so
   without this a toolbar group carrying the `hidden` attribute still renders --
   which is how the environment picker showed up on a page with 0 or 1 environment.
   Every toolbar group is covered (not just #ai-env-group) so the next one added
   inherits a working `hidden`. */
.ai-toolbar-group[hidden] {
    display: none;
}

.ai-toolbar-status {
    margin-left: auto;
}

.ai-toolbar-label {
    color: var(--theme-text-secondary);
    font-size: 0.85rem;
}

.ai-source-status {
    font-size: 0.8rem;
    padding: 3px 8px;
    border-radius: 4px;
}

.ai-source-live {
    color: var(--status-green);
}

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

.ai-button,
.ai-toggle-btn {
    background-color: var(--theme-bg-tertiary);
    color: var(--theme-text-primary);
    border: 1px solid var(--theme-border-primary);
    border-radius: 4px;
    padding: 5px 12px;
    font-size: 0.85rem;
    cursor: pointer;
}

.ai-button:hover,
.ai-toggle-btn:hover {
    background-color: var(--theme-bg-hover);
}

/* Compact variant of the shared .ai-button (e.g. inline row actions). */
.ai-button-sm { padding: 2px 8px; font-size: 0.8em; }

/* The "Live Metrics ↗" affordance (WD.7d) is an <a> styled as an ai-button —
   normalize the anchor so it matches the surrounding controls. */
.ai-beacon-lm-link,
.ai-beacon-lm-link:hover {
    display: inline-block;
    text-decoration: none;
    line-height: 1.2;
}

.ai-disabled-link {
    opacity: 0.5;
    pointer-events: none;
    cursor: default;
}

.ai-toggle-group {
    display: inline-flex;
    /* The SCTE Data tab made this a FIVE-button group; inline-flex with no wrap
       overflows its container at narrow widths instead of reflowing. */
    flex-wrap: wrap;
}

.ai-toggle-group .ai-toggle-btn {
    border-radius: 0;
    border-right-width: 0;
}

.ai-toggle-group .ai-toggle-btn:first-child {
    border-top-left-radius: 4px;
    border-bottom-left-radius: 4px;
}

.ai-toggle-group .ai-toggle-btn:last-child {
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
    border-right-width: 1px;
}

.ai-toggle-btn.active {
    background-color: var(--theme-bg-selected);
    border-color: var(--theme-link-color);
    color: var(--theme-text-primary);
    font-weight: 600;
}

.ai-select,
.ai-input {
    background-color: var(--theme-bg-primary);
    color: var(--theme-text-primary);
    border: 1px solid var(--theme-border-primary);
    border-radius: 4px;
    padding: 4px 8px;
    font-size: 0.85rem;
}

/* The Advinti ad-id allow-list takes a short comma set ("2,11"), so it does not
   need the width of a channel name. Sized to show a handful of ids without
   crowding the two date-time pickers beside it in the same filter row. */
.ai-adids-input {
    width: 10ch;
    min-width: 10ch;
}

/* ---- layout ---- */

.ai-dashboard {
    padding: 0 20px 40px;
}

.ai-panel {
    margin-bottom: 28px;
    background-color: var(--theme-bg-primary);
    border: 1px solid var(--theme-border-primary);
    border-radius: 6px;
    /* clip, not hidden: `hidden` makes the panel a scroll container, and the
       report's pinned horizontal scrollbar (.ai-hscroll, sticky) would then stick
       to the panel instead of the viewport. `hidden` stays as the fallback. */
    overflow: hidden;
    overflow: clip;
}

.ai-panel-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
    background-color: var(--theme-bg-tertiary);
    border-bottom: 1px solid var(--theme-border-primary);
}

.ai-panel-header h2 {
    margin: 0;
    font-size: 1.05rem;
}

.ai-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
}

.ai-filter {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.8rem;
    color: var(--theme-text-secondary);
}

/* ---- detection-method include/exclude checkboxes ---- */
/* An explicit include set (watermark / fingerprint / SCTE-35 / test) serialized
   to the backend `detection_method` comma-set param. Laid out as a wrapping row
   of checkbox chips beside the "Method" label. */
.ai-filter-methods {
    align-items: flex-start;
}

.ai-filter-label {
    padding-top: 2px;
}

.ai-method-checks {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 12px;
}

.ai-method-check {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    cursor: pointer;
    -webkit-user-select: none;
    user-select: none;
    white-space: nowrap;
}

.ai-method-check input {
    margin: 0;
    cursor: pointer;
}

/* Marks a method that CANNOT return a row in this report (#768: the Ad Detections
   statement carries `ad_id <> 0`, and SCTE-35 / test rows identify no creative).
   Muted rather than alarming — it is a scope fact, not a fault — but present, because
   a control that silently yields nothing reads as a broken report. */
.ai-method-check .ai-method-note {
    font-style: normal;
    font-size: 0.85em;
    color: #7a7a7a;
}

/* ---- pager ---- */

.ai-pager-wrap {
    padding: 8px 16px;
}

.ai-pager {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: 0.82rem;
    color: var(--theme-text-secondary);
}

.ai-page-btn {
    background-color: var(--theme-bg-tertiary);
    color: var(--theme-text-primary);
    border: 1px solid var(--theme-border-primary);
    border-radius: 4px;
    padding: 3px 10px;
    font-size: 0.8rem;
    cursor: pointer;
}

.ai-page-btn:hover:not(:disabled) {
    background-color: var(--theme-bg-hover);
}

.ai-page-btn:disabled {
    opacity: 0.5;
    cursor: default;
}

.ai-page-info {
    color: var(--theme-text-tertiary);
}

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

.ai-table-scroll {
    overflow-x: auto;
}

/* The Ad Detections / Ad Insertion report tables. ONE vertical scrollbar -- the
   page's (Howard 2026-10-02 ~16:12, superseding the P0 bounded box of earlier that
   day): the report has no inner vertical scroll, so the page scrolls through the
   last row of the current page. It still scrolls SIDEWAYS on its own; that bar is
   the pinned `.ai-hscroll` below (the box's own bar is hidden, as it would sit
   under the last row), so the columns past the viewport edge (Beacons, Fired,
   Correlation) never read as deleted. `overflow-y: hidden` cuts nothing: the box
   has no height limit, so it is always as tall as its table. */
.ai-table-scroll-page {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}

.ai-table-scroll-page::-webkit-scrollbar {
    display: none;
}

/* The report's horizontal scrollbar, pinned to the bottom of the viewport while
   the table is on screen (sticky inside the table host), and kept in step with the
   table by _syncHScroll. Hidden when nothing is wider than the box. */
.ai-hscroll {
    position: sticky;
    bottom: 0;
    z-index: 4;
    overflow-x: auto;
    overflow-y: hidden;
    background-color: var(--theme-bg-primary);
    border-top: 1px solid var(--theme-border-secondary);
}

.ai-hscroll[hidden] {
    display: none;
}

.ai-hscroll-spacer {
    height: 1px;
}

/* The first column (the row's timestamp) stays put while the rest scroll sideways,
   so a reader at Beacons / Fired still knows which row they are on. Opaque
   backgrounds are required: a sticky cell is painted OVER the cells sliding under
   it. The header cell is the corner, so it sits above both sticky planes. */
.ai-table-pin-first td:first-child,
.ai-table-pin-first th:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background-color: var(--theme-bg-primary);
    box-shadow: 1px 0 0 var(--theme-border-secondary);
}

.ai-table-pin-first thead th:first-child {
    z-index: 3;
    background-color: var(--theme-table-header-bg);
}

.ai-table-pin-first .ai-data-row:hover td:first-child {
    background-color: var(--theme-table-hover-bg);
}

/* The two row states that paint the ROW must reach the pinned cell too, because its
   opaque background covers the row's: the correlation flash, and the matched-row
   marker (a collapsed-border left edge is painted over by a sticky, z-indexed cell,
   so it is drawn as an inset shadow on the cell itself). */
.ai-table-pin-first .ai-flash td:first-child {
    animation: ai-flash-anim 2s ease-out;
}

.ai-table-pin-first .ai-corr-matched td:first-child {
    box-shadow: inset 3px 0 0 var(--theme-link-color), 1px 0 0 var(--theme-border-secondary);
}

/* A message row's single cell spans the table; pinning it would pin the message. */
.ai-table-pin-first .ai-row-message td:first-child {
    position: static;
}

.ai-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.82rem;
}

.ai-table th,
.ai-table td {
    padding: 7px 10px;
    border-bottom: 1px solid var(--theme-border-secondary);
    text-align: left;
    white-space: nowrap;
}

.ai-table thead th {
    position: sticky;
    top: 0;
    background-color: var(--theme-table-header-bg);
    color: var(--theme-text-secondary);
    font-weight: 600;
    border-bottom: 2px solid var(--theme-border-primary);
}

.ai-data-row {
    cursor: pointer;
}

.ai-data-row:hover {
    background-color: var(--theme-table-hover-bg);
}

.ai-corr-matched {
    border-left: 3px solid var(--theme-link-color);
}

.ai-flash {
    animation: ai-flash-anim 2s ease-out;
}

@keyframes ai-flash-anim {
    0% { background-color: var(--status-yellow); }
    100% { background-color: transparent; }
}

.ai-row-message td {
    padding: 18px;
    text-align: center;
}

/* ---- cell formatting ---- */

.ai-muted {
    color: var(--theme-text-tertiary);
}

/* An in-panel explanatory note. Used by the SCTE Data frame and -- load-bearingly --
   by the SCTE-35 Ad Pods tab, where a SUCCESSFUL read returning zero rows is the
   expected state today and must be distinguishable from a failed one. It had no rule
   at all until that tab needed it to actually be legible. */
.ai-note {
    margin: 8px 0 12px;
    padding: 8px 12px;
    border-left: 3px solid var(--theme-border-primary, #e0e0e0);
    background-color: var(--theme-bg-tertiary, #f6f7f9);
    color: var(--theme-text-secondary, #4a5568);
    font-size: 0.85rem;
    line-height: 1.45;
}

.ai-error {
    color: var(--status-red);
}

/* "Ad data not available on this server" — informational, not alarming. */
.ai-unavailable {
    color: var(--theme-text-secondary);
    display: inline-block;
    max-width: 70ch;
    line-height: 1.4;
}

.ai-pts,
.ai-mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
}

.ai-mono {
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    display: inline-block;
    vertical-align: bottom;
}

/* Echoed Token (Ad Data) that DIFFERS from the Channel Data token we send now
   (Howard 2026-10-02): the echo is an earlier epoch's token. Amber + dotted underline,
   the same "look closer" voice as .ai-unknown; the tooltip names both values. */
.ai-token-mismatch {
    color: var(--status-yellow);
    font-weight: 700;
    text-decoration: underline dotted;
    cursor: help;
}

.ai-url {
    color: var(--theme-link-color);
    max-width: 28ch;
    overflow: hidden;
    text-overflow: ellipsis;
    display: inline-block;
    vertical-align: bottom;
}

.ai-bool-yes {
    color: var(--status-green);
    font-weight: 700;
}

.ai-bool-no {
    color: var(--status-red);
    font-weight: 700;
}

.ai-badge {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 10px;
    font-size: 0.72rem;
    font-weight: 600;
    border: 1px solid transparent;
}

.ai-badge-watermark {
    background-color: var(--theme-bg-selected);
    color: var(--theme-link-color);
}

.ai-badge-fingerprint {
    background-color: var(--theme-bg-hover);
    color: var(--theme-text-secondary);
}

/* SCTE-35 is a SIGNALLED break rather than an inferred detection, so it is
   deliberately distinguishable from the two correlator-driven methods above
   rather than sharing their palette.
   ⚠️ It used to say exactly that while USING --theme-bg-selected, the very token
   .ai-badge-watermark uses -- so the two differed only in text colour. The class
   NAMES differed and the lockstep test asserts only that, which is why the
   collision survived. The badge-colour test now resolves these tokens through
   theme-variables.css and requires the five method backgrounds to be distinct in
   BOTH themes. */
.ai-badge-scte35 {
    background-color: var(--theme-status-blue-bg-light);
    color: var(--theme-status-blue-text);
    border-color: var(--theme-status-blue-border);
}

/* Synthetic/gated emitter rows (VisualSpliceVerify, SpliceScaleSeed). Real, but not
   production traffic.
   ⚠️ This used to share --theme-bg-tertiary with .ai-badge-neutral, differing only by
   one step of grey in the text colour -- so a REAL `test` detection rendered
   essentially identically to a method the dashboard does not recognise. That is the
   precise operator-facing failure the badge work was done to remove, reintroduced by
   the palette rather than by the code. */
.ai-badge-test {
    background-color: var(--theme-status-orange-bg-light);
    color: var(--theme-status-orange-text);
    border-color: var(--theme-status-orange-border);
}

.ai-badge-neutral {
    background-color: var(--theme-bg-tertiary);
    color: var(--theme-text-tertiary);
}

/* Insertion SOURCE badges (which intake wrote the row), one class per family of
   the closed vocabulary (SOURCE_VOCABULARY in ad-info-manager.js). A known source
   never renders .ai-badge-neutral (reserved for an UNRECOGNISED value), and a
   Sparrow row never borrows .ai-badge-accepted, which reads as "Accepted".
   Backgrounds are distinct from each other and from neutral in BOTH themes --
   asserted by ad-info-manager.insertion-formatters.test.js. */
.ai-badge-source-sparrow {
    background-color: var(--theme-status-blue-bg);
    color: var(--theme-status-blue-text);
    border-color: var(--theme-status-blue-border);
}

/* Visual Splice Verify: synthetic, same palette family as .ai-badge-test. */
.ai-badge-source-vsv {
    background-color: var(--theme-status-orange-bg-light);
    color: var(--theme-status-orange-text);
    border-color: var(--theme-status-orange-border);
}

/* Provenance not recorded: a pre-2026-09-27 row (the column default) or a row
   staged without a source. */
.ai-badge-source-unrecorded {
    background-color: var(--theme-status-yellow-bg);
    color: var(--theme-status-yellow-text);
    border-color: var(--theme-status-yellow-border);
    border-style: dashed;
}

.ai-badge-accepted {
    background-color: rgba(40, 167, 69, 0.15);
    color: var(--status-green);
}

.ai-badge-rejected {
    background-color: rgba(220, 53, 69, 0.15);
    color: var(--status-red);
}

/* Ad Insertion OUTCOME pills (Howard FINAL stamp + colours, 2026-09-28): exactly
   four, bordered, easy to scan down the table.
     Rejected  red    -- we refused the Sparrow/VSV request itself
     Failed    amber  -- OUR splice could not complete the insert
     Missed    gray   -- the window had already passed
     Inserted  green  -- delivered
   Each pairs a status BACKGROUND token with its TEXT token and a saturated
   border, so all four are distinct from each other AND from the Source badges
   in the same row in BOTH themes -- asserted by ad-info-manager.outcome.test.js,
   which resolves the tokens through theme-variables.css. Missed is gray (Howard's
   amend allows it) because blue is the Sparrow source badge's colour. The value
   comes from the API's `outcome` field; the dashboard never re-derives it. */
.ai-badge-outcome {
    border-width: 1.5px;
    border-style: solid;
    font-weight: 700;
    letter-spacing: 0.01em;
}

.ai-badge-outcome-rejected {
    background-color: var(--theme-status-red-bg);
    color: var(--theme-status-red-text);
    border-color: var(--status-red);
}

.ai-badge-outcome-failed {
    background-color: var(--theme-status-orange-bg);
    color: var(--theme-status-orange-text);
    border-color: var(--status-orange);
}

.ai-badge-outcome-missed {
    background-color: var(--theme-status-gray-bg);
    color: var(--theme-status-gray-text);
    border-color: var(--status-gray);
}

.ai-badge-outcome-inserted {
    background-color: var(--theme-status-green-bg);
    color: var(--theme-status-green-text);
    border-color: var(--status-green);
}

/* Pending (Howard 2026-09-30: a bare dash read as a missing value). PURPLE --
   blue is the Sparrow source badge, and light-theme amber is shared with Failed --
   and DASHED, because the state is transient: it becomes one of the four above. */
.ai-badge-outcome-pending {
    background-color: var(--theme-status-purple-bg);
    color: var(--theme-status-purple-text);
    border-color: var(--status-purple);
    border-style: dashed;
}

/* Departed (2026-10-02): outlined, dotted gray -- Missed's family (no fault of
   ours) but unfilled, so the two read apart. */
.ai-badge-outcome-departed {
    background-color: transparent;
    color: var(--theme-text-secondary);
    border-color: var(--status-gray);
    border-style: dotted;
}

/* The Req->Splice cell (formatRequestToSplice): a request->splice delay on an
   Inserted row, else the slot lead ("lead X") or how late the request was
   ("late X"). Late is red text, the one reading an operator must not miss. */
.ai-timing {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    cursor: help;
}

.ai-timing-ok {
    color: var(--theme-status-green-text);
}

.ai-timing-lead {
    color: var(--theme-text-secondary);
}

.ai-timing-late {
    color: var(--status-red);
    font-weight: 700;
}

/* The viewer-edge margin under an Inserted row's delay: one small line, so the two
   numbers Howard compares when tuning the delay sit in one cell. */
.ai-timing-sub {
    display: block;
    font-size: 0.7rem;
}

/* The Outcome's Detail cell: reason text wraps rather than widening the table.
   The min-width is load-bearing (Howard P0 2026-10-02): `overflow-wrap: anywhere`
   makes the cell's min-content width ONE CHARACTER, so a table wider than its box
   (every Ad Insertion table at a normal viewport) squeezed Detail to a sliver and
   wrapped "no splice observed" letter by letter into rows ten lines tall. With a
   floor it wraps by words in a readable column; the table scrolls instead. */
.ai-outcome-detail {
    display: inline-block;
    min-width: 12rem;
    max-width: 22rem;
    white-space: normal;
    overflow-wrap: anywhere;
}

/* Ad Insertion OUTCOME CHECKBOXES (Howard 2026-10-02; the design rev 5 §4 toggle
   chips, now a real checkbox inside each). The outcome chips use the pill colours
   above -- FILLED (the pill's background + text) when ticked (`ai-chip-on`, set
   from the filter state), OUTLINED (the saturated border only, on the page
   background) when not -- so the box for an outcome looks like its pill. Pending
   is a small MUTED chip (Howard G5 q2 A): dashed, and deliberately unlike any of
   the four pills. The chip colours are asserted distinct in BOTH themes by
   ad-info-manager.outcome-filters.test.js. */
.ai-outcome-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    margin: 4px 0 8px;
}

.ai-outcome-bar-label {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--theme-text-secondary);
}

.ai-outcome-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.ai-outcome-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 10px;
    border-radius: 12px;
    border-width: 1.5px;
    border-style: solid;
    font-size: 0.76rem;
    font-weight: 700;
    cursor: pointer;
    background-color: transparent;
}

.ai-outcome-chip input,
.ai-outcome-preset input {
    margin: 0;
    cursor: pointer;
}

.ai-outcome-chip:focus-within,
.ai-outcome-preset:focus-within,
.ai-outcome-preset-btn:focus-visible {
    outline: 2px solid var(--theme-link-color);
    outline-offset: 2px;
}

.ai-chip-count {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.ai-outcome-chip-rejected {
    border-color: var(--status-red);
    color: var(--status-red);
}

.ai-outcome-chip-rejected.ai-chip-on {
    background-color: var(--theme-status-red-bg);
    color: var(--theme-status-red-text);
}

.ai-outcome-chip-failed {
    border-color: var(--status-orange);
    color: var(--status-orange);
}

.ai-outcome-chip-failed.ai-chip-on {
    background-color: var(--theme-status-orange-bg);
    color: var(--theme-status-orange-text);
}

.ai-outcome-chip-missed {
    border-color: var(--status-gray);
    color: var(--status-gray);
}

.ai-outcome-chip-missed.ai-chip-on {
    background-color: var(--theme-status-gray-bg);
    color: var(--theme-status-gray-text);
}

.ai-outcome-chip-inserted {
    border-color: var(--status-green);
    color: var(--status-green);
}

.ai-outcome-chip-inserted.ai-chip-on {
    background-color: var(--theme-status-green-bg);
    color: var(--theme-status-green-text);
}

/* Pending: smaller and dashed, in the Pending pill's purple -- present so a
   pending row never vanishes silently when a chip is toggled. */
.ai-outcome-chip-pending {
    padding: 1px 8px;
    border-width: 1px;
    border-style: dashed;
    border-color: var(--status-purple);
    color: var(--theme-status-purple-text);
    font-size: 0.7rem;
    font-weight: 500;
}

.ai-outcome-chip-pending.ai-chip-on {
    background-color: var(--theme-status-purple-bg);
    color: var(--theme-status-purple-text);
}

/* Departed (split from Missed server-side): the viewer had left, nothing of ours
   failed -- an OUTLINED, dotted gray, beside Missed's filled gray. */
.ai-outcome-chip-departed {
    border-color: var(--status-gray);
    border-style: dotted;
    color: var(--theme-text-secondary);
}

.ai-outcome-chip-departed.ai-chip-on {
    background-color: var(--theme-bg-secondary);
    color: var(--theme-text-primary);
}

/* Problems: a checkbox meaning exactly Rejected + Failed + Missed. */
.ai-outcome-preset {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 10px;
    border-radius: 12px;
    border: 1px solid var(--theme-link-color);
    color: var(--theme-link-color);
    font-size: 0.74rem;
    cursor: pointer;
}

.ai-outcome-preset-btn {
    padding: 2px 8px;
    border-radius: 4px;
    border: 1px solid var(--theme-border-primary);
    background: none;
    color: var(--theme-text-secondary);
    font-size: 0.74rem;
    cursor: pointer;
}

.ai-outcome-preset:hover,
.ai-outcome-preset-btn:hover {
    background-color: var(--theme-bg-selected);
}

/* "This server cannot filter by outcome" / reason-reset note. */
.ai-filter-note {
    margin: 0 0 8px;
    padding: 6px 12px;
    border-left: 3px solid var(--status-yellow);
    border-radius: 4px;
    background-color: var(--theme-bg-secondary);
    color: var(--theme-text-secondary);
    font-size: 0.8rem;
}

/* The Failed attention strip (on-page only, Howard G5 q4 A). */
.ai-failed-strip {
    margin: 0 0 8px;
    padding: 6px 12px;
    border: 1px solid var(--status-orange);
    border-left-width: 4px;
    border-radius: 4px;
    background-color: var(--theme-status-orange-bg);
    color: var(--theme-status-orange-text);
    font-size: 0.82rem;
}

.ai-failed-strip .ai-strip-link {
    color: inherit;
    text-decoration: underline;
}

/* Outlined pill buttons: the "🔗 matched" correlation affordance and the inline
   "View Test7 ad activity" affordance in the "ad data not available" state. */
.ai-corr-btn,
.ai-switch-adenv {
    background: none;
    border: 1px solid var(--theme-link-color);
    color: var(--theme-link-color);
    border-radius: 12px;
    padding: 1px 9px;
    font-size: 0.72rem;
    cursor: pointer;
}

.ai-corr-btn:hover,
.ai-switch-adenv:hover {
    background-color: var(--theme-bg-selected);
}

/* The env-switch pill sits inline in a wrapping sentence — keep it on one line
   and a touch larger than the correlation pill. */
.ai-switch-adenv {
    padding: 1px 10px;
    font-size: 0.78rem;
    white-space: nowrap;
}

/* ---- detail pane ---- */

.ai-detail-pane {
    display: none;
}

.ai-detail-pane.ai-detail-open {
    display: block;
    margin-top: 8px;
    background-color: var(--theme-bg-secondary);
    border: 1px solid var(--theme-link-color);
    border-radius: 6px;
}

.ai-detail-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 16px;
    border-bottom: 1px solid var(--theme-border-primary);
}

.ai-detail-header h3 {
    margin: 0;
    font-size: 1rem;
}

.ai-detail-close {
    background: none;
    border: 1px solid var(--theme-border-primary);
    color: var(--theme-text-secondary);
    border-radius: 4px;
    padding: 2px 8px;
    cursor: pointer;
}

.ai-detail-close:hover {
    background-color: var(--theme-bg-hover);
}

.ai-detail-body {
    display: flex;
    flex-wrap: wrap;
    gap: 24px;
    padding: 16px;
}

.ai-detail-section {
    flex: 1 1 320px;
    min-width: 280px;
}

.ai-detail-section h4 {
    margin: 0 0 8px;
    font-size: 0.9rem;
    color: var(--theme-text-secondary);
}

.ai-corr-keytag {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.72rem;
    color: var(--theme-text-tertiary);
    font-weight: 400;
}

.ai-record {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 4px 14px;
    margin: 0 0 12px;
    font-size: 0.82rem;
}

.ai-record dt {
    color: var(--theme-text-tertiary);
}

.ai-record dd {
    margin: 0;
    color: var(--theme-text-primary);
    word-break: break-word;
}

/* The Ad Detections / Ad Insertion pop-up's STACKED fields (Howard 2026-10-02):
   label, then the value with its Copy button, one field under another. A long
   GUID / URL / token wraps (`overflow-wrap: anywhere`) instead of widening the
   pop-up, so no field needs a horizontal scrollbar to be read. Scoped to
   `.ai-record-stacked`: the SparrowTV event pane and the AdPrep `.ap-kv` lists use
   the plain two-column `.ai-record` above with bare dt/dd, and must keep it. */
.ai-record.ai-record-stacked {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.ai-record-stacked .ai-record-field {
    min-width: 0;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--theme-border-secondary);
}

.ai-record-stacked dt {
    margin-bottom: 2px;
    font-size: 0.74rem;
}

.ai-record-stacked dd {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

/* The one-line plain-language help under each pop-up field (FIELD_HELP in
   ad-info-manager.js): a second <dd>, so it sits under the value and its Copy
   button and is never part of what Copy copies. */
.ai-record-stacked dd.ai-record-help {
    display: block;
    margin-top: 2px;
    font-size: 0.72rem;
    line-height: 1.35;
    color: var(--theme-text-tertiary);
}

.ai-record-value {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* The row pop-up shows EVERY field IN FULL (Howard 2026-09-28): the table's
   width-capping ellipsis on GUIDs, URLs and details does not apply here. */
.ai-record dd .ai-mono,
.ai-record dd .ai-url,
.ai-record dd .ai-outcome-detail {
    display: inline;
    max-width: none;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
    overflow-wrap: anywhere;
}

/* ======================================================================== */
/* SparrowTV Session section                                                */
/* ======================================================================== */

/* ---- in-page section toggle ---- */

.ai-section-nav {
    padding: 12px 20px 0;
}

.ai-section-nav .ai-toggle-btn {
    padding: 6px 16px;
    font-size: 0.9rem;
}

.ai-section-hidden {
    display: none;
}

/* ---- connection / session status cards ---- */

.stv-status-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px;
    padding: 16px;
}

.stv-card {
    background-color: var(--theme-bg-secondary);
    border: 1px solid var(--theme-border-primary);
    border-left-width: 3px;
    border-radius: 6px;
    padding: 12px 14px;
}

.stv-card-up {
    border-left-color: var(--status-green);
}

.stv-card-down {
    border-left-color: var(--status-red);
}

.stv-card-unknown {
    border-left-color: var(--theme-border-primary);
}

.stv-card-head {
    display: flex;
    align-items: center;
    gap: 8px;
}

.stv-card-title {
    font-weight: 600;
    font-size: 0.95rem;
}

.stv-card-state {
    margin-left: auto;
    font-size: 0.72rem;
    color: var(--theme-text-tertiary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.stv-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex: 0 0 auto;
    display: inline-block;
}

.stv-dot-up {
    background-color: var(--status-green);
    box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.18);
}

.stv-dot-down {
    background-color: var(--status-red);
    box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.18);
}

.stv-dot-unknown {
    background-color: var(--theme-text-tertiary);
}

.stv-card-path {
    margin: 6px 0 10px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.72rem;
    color: var(--theme-text-tertiary);
}

.stv-card-empty {
    font-size: 0.8rem;
}

.stv-card-stats {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 3px 12px;
    margin: 0;
    font-size: 0.8rem;
}

.stv-card-stats dt {
    color: var(--theme-text-tertiary);
}

.stv-card-stats dd {
    margin: 0;
    color: var(--theme-text-primary);
    text-align: right;
}

/* ---- Ad Activity detection-activity chart ---- */

.ai-chart-host {
    min-height: 230px;
    padding: 8px 16px 0;
}

#ai-detections-chart-note,
#ai-insertions-chart-note {
    padding: 4px 16px 12px;
    margin: 0;
}

/* ---- rate / success charts ---- */

.stv-chart-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
    gap: 16px;
    padding: 16px;
}

.stv-chart-card {
    background-color: var(--theme-bg-secondary);
    border: 1px solid var(--theme-border-primary);
    border-radius: 6px;
    padding: 10px 12px;
}

.stv-chart-card h3 {
    margin: 0 0 8px;
    font-size: 0.88rem;
    color: var(--theme-text-secondary);
}

.stv-chart-host {
    min-height: 230px;
}

#stv-chart-note {
    padding: 0 16px 12px;
    margin: 0;
}

/* ---- event-log outcome / endpoint badges + failure rows ---- */

.stv-row-failure {
    background-color: rgba(220, 53, 69, 0.06);
}

.ai-badge-success {
    background-color: rgba(40, 167, 69, 0.15);
    color: var(--status-green);
}

.ai-badge-failure {
    background-color: rgba(220, 53, 69, 0.15);
    color: var(--status-red);
}

.ai-badge-info {
    background-color: var(--theme-bg-hover);
    color: var(--theme-text-secondary);
}

.ai-badge-ep {
    background-color: var(--theme-bg-tertiary);
    color: var(--theme-text-secondary);
    border-color: var(--theme-border-secondary);
}

.ai-badge-ep-master {
    color: #2196F3;
}

.ai-badge-ep-channel_data {
    color: #4CAF50;
}

.ai-badge-ep-ad_control {
    color: #FF9800;
}

.ai-badge-ep-ad_data {
    color: #9C27B0;
}

/* ---- Beacon "Fires by source" (provenance) card (Leg B, 0-surface) ---- */

/* The per-source breakdown sits above the per-creative funnel with a divider so
   the two rollups read as distinct blocks. */
.ai-beacon-source {
    margin: 16px 0 20px;
    padding-top: 12px;
    border-top: 1px solid var(--theme-border-primary);
}

.ai-subhead {
    margin: 0 0 2px;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--theme-text-primary);
}

/* The tombstone note is deliberately quieter than the count-semantics caption —
   a boxed aside so it reads as a standing reminder, not per-refresh data. */
.ai-beacon-prover-tombstone {
    margin-top: 10px;
    padding: 8px 10px;
    border-left: 2px solid var(--theme-border-secondary);
    background: var(--theme-bg-secondary);
    color: var(--theme-text-tertiary);
    font-size: 0.8rem;
}

/* The provenance name cell (Player / Verify / probe). A styling hook for the label;
   the muted variant for a synthesized zero row rides the co-class `.ai-muted`. */
.ai-source-label {
    font-weight: 600;
}

/* Synthesized zero rows (a provenance with no fires) render muted so real,
   non-zero provenance rows stand out. */
.ai-source-table .ai-row-empty td {
    color: var(--theme-text-tertiary);
}

/* The per-refresh windowed-count footnote under the source table — quiet, so it
   reads as a scope caveat on the counts above, not as headline data. */
.ai-source-window {
    margin: 6px 0 0;
    color: var(--theme-text-tertiary);
    font-size: 0.8rem;
}

/* ---- shared form primitives ----
   The WD.10 S2 operator sign-in gate that introduced these lived on
   AdInformation and was REMOVED by Howard ruling 458a (2026-08-06) — that
   dashboard is per-server and login-free, so `.ai-operator-login` is gone.
   The field/input/status rules below STAY: AdPrepManagementDashboard and
   AdPartnerPortal load this stylesheet and use `.ai-field` / `.ai-input` /
   `.ai-status` for their own forms (ad-prep-app.js, ad-prep-manager.js,
   ad-partner-portal.js). Do not remove them as "dead login styling." */

.ai-field {
    display: block;
    margin-bottom: 12px;
    font-size: 0.85rem;
    color: var(--theme-text-secondary);
}

.ai-field .ai-input {
    display: block;
    width: 100%;
    margin-top: 4px;
    box-sizing: border-box;
}

.ai-status {
    margin-top: 12px;
    font-size: 0.85rem;
    min-height: 1.2em;
}

.ai-status-error {
    color: var(--status-red, #dc3545);
}


/* Live Viewers: show-test toggle (Howard 2026-09-29) */
.ai-filter-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    user-select: none;
}
.ai-filter-toggle .ai-checkbox {
    margin: 0;
}

/* Row detail pop-up: sticky overlay + one-click copy (Howard 2026-09-29). The
   SparrowTV event pane still uses this in-flow sticky form. */
.ai-detail-pane.ai-detail-open {
    position: sticky;
    top: 8px;
    z-index: 20;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
    max-height: calc(100vh - 24px);
    overflow: auto;
}

/* The Ad Detections / Ad Insertion row pop-up is a MODAL CENTRED in the viewport
   (Howard 2026-10-02 ~16:12; `ai-detail-popup`, set by _renderDetailPane). It was a
   region after the table, always scrolled off screen. It scrolls vertically only
   (its fields stack and wrap) and its header -- the record key and Close -- stays
   on screen while it does. The backdrop (1100) and the pop-up (1101) sit above the
   whole page, the 32 px dashboard switcher bar (1000) included; the pop-up is
   centred in the viewport BELOW that bar (top: 50% + 16 px), so it never covers it. */
.ai-detail-pane.ai-detail-popup.ai-detail-open {
    position: fixed;
    top: calc(50% + 16px);
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(1200px, calc(100vw - 32px));
    max-height: calc(100vh - 32px - 32px);
    margin-top: 0;
    z-index: 1101;
    overflow-x: hidden;
    overflow-y: auto;
}

.ai-detail-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1100;
    background-color: rgba(0, 0, 0, 0.45);
}

.ai-detail-backdrop[hidden] {
    display: none;
}

.ai-detail-popup .ai-detail-header {
    position: sticky;
    top: 0;
    z-index: 1;
    background-color: var(--theme-bg-secondary);
}
.ai-detail-popup .ai-detail-section {
    min-width: min(280px, 100%);
}
.ai-detail-header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* The pop-up's per-field Copy buttons, one beside each value. */
.ai-field-copy {
    background: none;
    border: 1px solid var(--theme-border-primary);
    color: var(--theme-text-secondary);
    border-radius: 4px;
    cursor: pointer;
}
.ai-field-copy {
    flex: none;
    padding: 0 8px;
    font-size: 0.74rem;
    line-height: 1.6;
}
.ai-field-copy:hover {
    background-color: var(--theme-bg-hover);
}
.ai-field-copy.ai-copied {
    border-color: var(--theme-link-color);
    color: var(--theme-link-color);
}
.ai-field-copy.ai-copy-failed {
    border-color: var(--theme-status-red-text);
    color: var(--theme-status-red-text);
}

/* Read by a screen reader (the copy result), not shown. */
.ai-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Ad Insertion COLUMN GROUPS (Howard 2026-10-02): one checkbox per group (a partly
   shown group is indeterminate), each with its shown/total count, then Show all
   columns / Default columns. */
.ai-column-groups {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
    margin: 4px 0 8px;
}

.ai-column-groups:empty {
    display: none;
}

.ai-column-count {
    font-size: 0.85em;
    color: var(--theme-text-tertiary);
}

/* Column GROUP colours (Howard 2026-10-02 night): a group's checkbox and every header
   of a column in that group carry the same colour, so ticking a group shows which
   headers it adds or removes. Identification only. `ai-colgroup-<id>` picks the
   group's --theme-colgroup-* token; `ai-colgroup` paints with it. */
.ai-colgroup-identity {
    --ai-colgroup: var(--theme-colgroup-identity);
}

.ai-colgroup-geo {
    --ai-colgroup: var(--theme-colgroup-geo);
}

.ai-colgroup-creative {
    --ai-colgroup: var(--theme-colgroup-creative);
}

.ai-colgroup-timing {
    --ai-colgroup: var(--theme-colgroup-timing);
}

.ai-colgroup-outcome {
    --ai-colgroup: var(--theme-colgroup-outcome);
}

.ai-colgroup-other {
    --ai-colgroup: var(--theme-colgroup-other);
}

.ai-method-check.ai-colgroup {
    padding: 2px 8px 2px 6px;
    border: 1px solid var(--ai-colgroup);
    border-left-width: 5px;
    border-radius: 4px;
    background-color: color-mix(in srgb, var(--ai-colgroup) 14%, transparent);
}

.ai-method-check.ai-colgroup input {
    accent-color: var(--ai-colgroup);
}

/* The header: a bar of the group colour across its top and a tint of it. The pinned
   first header keeps its own right-edge shadow beside the bar. */
.ai-table thead th.ai-colgroup {
    background-color: color-mix(in srgb, var(--ai-colgroup) 14%, var(--theme-table-header-bg));
    box-shadow: inset 0 5px 0 var(--ai-colgroup);
}

.ai-table-pin-first thead th.ai-colgroup:first-child {
    box-shadow: inset 0 5px 0 var(--ai-colgroup), 1px 0 0 var(--theme-border-secondary);
}

.ai-columns-none {
    padding: 16px;
    margin: 0;
}
