/* ============================================================================
   Clearview "Console" — Reflex DB
   ----------------------------------------------------------------------------
   Requires tokens/tokens.css + css/clearview-refresh.css + css/media.css
   (app shell). Namespaced `cvr-`.

   THE DIAGNOSIS
   reflex-db.js is 11,109 lines and 308 functions holding FIVE products behind
   three tabs: database CRUD, a spreadsheet grid, a drag-and-drop page builder,
   a run-of-show timeline, and notification/WhatsApp config. It has 419 inline
   style attributes and 9 `.reflex-*` rules in styles.css — this page is
   essentially unstyled by the stylesheet, which is why a CSS-level refresh has
   had almost no effect on it.

   Concrete problems this design answers:

   - `switchOverlayTab()` (:3034) re-runs the ENTIRE loadReflexDB(), refetching
     databases, sites, ROS schedules and page-builder layouts on every tab click.
     `showOverlayDbTab()` (:3071) then refetches the database again.
   - Every database's card shell and every ROS schedule's view are rendered
     up-front and toggled with `.active` (:600, :8274), so DOM size scales with
     the TOTAL number of databases, not the visible one.
   - The database tab strip grows one tab per database AND per sub-database. At
     twenty databases it is unusable, and a sub-DB is visually identical to a DB
     apart from a "Sub" badge.
   - THREE separate mechanisms cope with too many columns — hide, resize,
     reorder — each with its own modal, its own global, and its own localStorage
     key (overlay_hidden_columns / overlay_column_widths / overlay_column_order).
   - Go Live is the entire point of the overlay system: exactly one row per
     database is `selectedId`, and the broadcast graphics render that row. It is
     currently a small toggle in a checkbox cell.

   THE RESPONSE
   1. A searchable database picker replaces the tab strip; sub-DBs nest under
      their parent instead of impersonating one.
   2. ONE column panel — visibility, order and width in a single control backed
      by a single state object.
   3. Go Live becomes the hero: a persistent live banner, a full-row treatment,
      and a dedicated column that reads as broadcast state, not as a checkbox.
   ============================================================================ */

/* ================================================================== live row
   `selectedId` is what the overlay actually renders. It deserves to look like
   broadcast state. */
.cvr-live-bar {
  display: flex; align-items: center; gap: var(--cv-space-2);
  padding: var(--cv-space-2) var(--cv-space-3);
  border: 1px solid var(--cv-danger-edge);
  border-left-width: 3px;
  border-radius: var(--cv-radius);
  background: var(--cv-danger-wash);
}
.cvr-live-bar[data-live="false"] {
  border-color: var(--cv-border);
  border-left-color: var(--cv-border-strong);
  background: var(--cv-bg-raised);
}
.cvr-live-tag {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px var(--cv-space-2); border-radius: var(--cv-radius-sm);
  background: var(--cv-danger-solid); color: var(--cv-on-danger);
  font-size: 9px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase;
}
.cvr-live-bar[data-live="false"] .cvr-live-tag { background: var(--cv-bg-active); color: var(--cv-text-muted); }
.cvr-live-dot { width: 7px; height: 7px; border-radius: var(--cv-radius-full); background: currentColor;
                animation: cv-pulse 1.6s var(--cv-ease) infinite; }
@media (prefers-reduced-motion: reduce) { .cvr-live-dot { animation: none; } }
.cvr-live-vals { display: flex; gap: var(--cv-space-3); flex-wrap: wrap; min-width: 0; font-size: var(--cv-text-2xs); }
.cvr-live-vals b { font-weight: var(--cv-weight-medium); color: var(--cv-text); }
/* These sit on the danger wash, where --cv-text-muted drops under 4:1 in the
   light theme. They are field LABELS for what is on air — not decoration. */
.cvr-live-vals span { color: var(--cv-text-secondary); }

/* ===================================================================== grid */
/* The grid takes the height that is left, rather than a fixed slice of it. A
   hard vh cap leaves a dead band under the table on a tall monitor and squeezes
   it to a few rows on a laptop. */
.cvr-grid-wrap { max-height: calc(100vh - 300px); min-height: 320px;
  position: relative; overflow: auto; border: 1px solid var(--cv-border); border-radius: var(--cv-radius-lg); }
.cvr-grid { border-collapse: separate; border-spacing: 0; width: max-content; min-width: 100%; }

.cvr-grid th {
  position: sticky; top: 0; z-index: var(--cv-z-sticky);
  height: var(--cv-row); padding: 0 var(--cv-space-2);
  background: var(--cv-bg-raised);
  border-bottom: 1px solid var(--cv-border);
  border-right: 1px solid var(--cv-border-subtle);
  font-size: 9px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase;
  color: var(--cv-text-secondary); text-align: left; white-space: nowrap;
  user-select: none;   /* already position:sticky above — which IS a containing
                          block for .cvr-resize. Do not add position:relative
                          here: it wins on order and kills the sticky header. */
}
/* Resizing lives on the header edge, where the column actually is — the third
   of the three mechanisms, folded back into the grid instead of a modal. The
   grip is 7px wide but sits inside a 44px-tall header, so the target is fine. */
.cvr-resize {
  position: absolute; top: 0; right: -3px; bottom: 0; width: 7px;
  cursor: col-resize; z-index: 1;
}
.cvr-resize::after {
  content: ""; position: absolute; top: 6px; bottom: 6px; left: 3px; width: 1px;
  background: transparent; transition: background var(--cv-dur-fast) var(--cv-ease);
}
.cvr-resize:hover::after, .cvr-grid[data-resizing="true"] .cvr-resize::after { background: var(--cv-accent); }
.cvr-grid[data-resizing="true"] { cursor: col-resize; user-select: none; }
.cvr-grid th { cursor: pointer; }
.cvr-grid th[aria-sort] { color: var(--cv-accent); }
.cvr-grid td {
  height: var(--cv-row-dense); padding: 0 var(--cv-space-2);
  border-bottom: 1px solid var(--cv-border-subtle);
  border-right: 1px solid var(--cv-border-subtle);
  max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--cv-text-2xs);
}
.cvr-grid tbody tr:hover td { background: var(--cv-bg-hover); }

/* The first DATA column sticks, not just the checkboxes. With thirty
   user-defined fields, scrolling right currently loses the only thing that
   identifies the row you are looking at. */
.cvr-grid .cvr-pin {
  position: sticky; z-index: 2;
  background: var(--cv-bg-surface);
  border-right: 1px solid var(--cv-border);
}
/* A pinned header is sticky on BOTH axes, so it has to outrank the ordinary
   sticky headers — otherwise the unpinned ones paint over it as you scroll
   right and the corner reads as two labels stacked. */
.cvr-grid th.cvr-pin { z-index: calc(var(--cv-z-sticky) + 1); background: var(--cv-bg-raised); }
.cvr-grid .cvr-pin-0 { left: 0; width: 34px; padding: 0; }   /* .cv-cellcheck fills it */
.cvr-grid .cvr-pin-1 { left: 34px; width: 52px; }
.cvr-grid .cvr-pin-2 { left: 86px; }
/* Every state colour in the app is a translucent wash. On a STICKY cell that
   is a bug, not a style choice: the rows scrolling underneath show straight
   through. Pinned cells therefore paint the wash as a gradient layer over an
   opaque base — same colour on screen, but solid. The `td.cvr-pin` selectors
   are one class more specific than the row-state rules above them, which is
   what keeps the opaque base from being overwritten by the shorthand. */
.cvr-grid tbody tr:hover td.cvr-pin {
  background-color: var(--cv-bg-surface);
  background-image: linear-gradient(var(--cv-bg-hover), var(--cv-bg-hover));
}

/* Live row: unmistakable across its full width, and it survives horizontal
   scroll because the pinned cells carry it too. */
.cvr-grid tr[data-live="true"] td { background: var(--cv-danger-wash); }
.cvr-grid tr[data-live="true"] td.cvr-pin {
  background-color: var(--cv-bg-surface);
  background-image: linear-gradient(var(--cv-danger-wash), var(--cv-danger-wash));
}
.cvr-grid tr[data-live="true"] .cvr-pin-0 { box-shadow: inset 3px 0 0 var(--cv-danger); }

.cvr-grid tr[aria-selected="true"] td { background: var(--cv-accent-wash); }
.cvr-grid tr[aria-selected="true"] td.cvr-pin {
  background-color: var(--cv-bg-surface);
  background-image: linear-gradient(var(--cv-accent-wash), var(--cv-accent-wash));
}

/* Go Live control. A switch, because it is a state you put a row into, not an
   action you fire — and only one row can hold it. */
.cvr-golive {
  display: grid; place-items: center;
  width: 100%; height: 100%;
  border: none; background: none; cursor: pointer; padding: 0;
}
.cvr-golive-track {
  width: 28px; height: 15px; border-radius: var(--cv-radius-full);
  background: var(--cv-bg-active); border: 1px solid var(--cv-border-strong);
  position: relative; transition: background var(--cv-dur-fast) var(--cv-ease),
                                  border-color var(--cv-dur-fast) var(--cv-ease);
}
.cvr-golive-knob {
  position: absolute; top: 1px; left: 1px;
  width: 11px; height: 11px; border-radius: var(--cv-radius-full);
  background: var(--cv-text-muted);
  transition: translate var(--cv-dur-fast) var(--cv-ease), background var(--cv-dur-fast) var(--cv-ease);
}
.cvr-golive:hover .cvr-golive-track { border-color: var(--cv-danger); }
.cvr-golive[aria-checked="true"] .cvr-golive-track { background: var(--cv-danger); border-color: var(--cv-danger); }
.cvr-golive[aria-checked="true"] .cvr-golive-knob { translate: 13px 0; background: #fff; }

.cvr-cell-empty { color: var(--cv-text-disabled); font-style: italic; }
.cvr-cell-num { font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums; }

/* ========================================================= database picker
   Replaces a tab strip that grew one tab per database and per sub-database. */
.cvr-dbbtn {
  display: inline-flex; align-items: center; gap: var(--cv-space-2);
  height: var(--cv-control); padding: 0 var(--cv-space-2);
  border: 1px solid var(--cv-border); border-radius: var(--cv-radius);
  background: var(--cv-bg-surface); color: var(--cv-text);
  font: inherit; cursor: pointer; max-width: 320px;
}
.cvr-dbbtn:hover { background: var(--cv-bg-hover); border-color: var(--cv-border-strong); }
.cvr-dbbtn[aria-expanded="true"] { border-color: var(--cv-accent); }
.cvr-dbbtn > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cvr-dbmenu { min-width: 340px; max-height: 400px; display: flex; flex-direction: column; }
.cvr-dbmenu-list { overflow-y: auto; padding: var(--cv-space-1); }
.cvr-dbitem {
  display: grid; grid-template-columns: 14px minmax(0, 1fr) auto auto;
  gap: var(--cv-space-2); align-items: center;
  width: 100%; min-height: var(--cv-row); padding: 0 var(--cv-space-2);
  border: none; border-radius: var(--cv-radius-sm);
  background: none; color: var(--cv-text); font: inherit; text-align: left; cursor: pointer;
}
.cvr-dbitem:hover { background: var(--cv-bg-hover); }
.cvr-dbitem[aria-current="true"] { background: var(--cv-accent-wash); color: var(--cv-accent); }
/* A sub-database is indented and marked. In the current UI it is a tab that
   looks exactly like a parent tab with a small badge. */
.cvr-dbitem[data-sub="true"] { padding-left: var(--cv-space-5); }
.cvr-dbitem[data-sub="true"]::before {
  content: ""; position: absolute; left: 22px; width: 1px; height: 100%;
  background: var(--cv-border-subtle);
}
.cvr-dbmeta {
  font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--cv-text-2xs); color: var(--cv-text-disabled);
}
.cvr-dbmenu-list { position: relative; }

/* ============================================================ column panel
   One control for visibility, order and width — replacing three modals, three
   globals and three localStorage keys. */
.cvr-colpanel { width: 300px; max-height: 400px; display: flex; flex-direction: column; }
.cvr-colpanel-head {
  display: flex; align-items: center; gap: var(--cv-space-2);
  padding: var(--cv-space-2); border-bottom: 1px solid var(--cv-border-subtle);
  font-size: 9px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase; color: var(--cv-text-disabled);
}
.cvr-collist { overflow-y: auto; padding: var(--cv-space-1); }
.cvr-col {
  display: grid; grid-template-columns: 16px 16px minmax(0, 1fr) auto;
  gap: var(--cv-space-2); align-items: center;
  min-height: var(--cv-row); padding: 0 var(--cv-space-1);
  border-radius: var(--cv-radius-sm);
  font-size: var(--cv-text-2xs);
}
.cvr-col:hover { background: var(--cv-bg-hover); }
.cvr-col[data-dragging="true"] { opacity: 0.4; }
.cvr-col[data-over="true"] { box-shadow: inset 0 2px 0 var(--cv-accent); }
.cvr-col-grip { color: var(--cv-text-disabled); cursor: grab; }
.cvr-col-grip:active { cursor: grabbing; }
.cvr-col[data-hidden="true"] .cvr-col-name { color: var(--cv-text-disabled); text-decoration: line-through; }
.cvr-col-w {
  font-family: var(--cv-font-mono); font-size: 9px; color: var(--cv-text-disabled);
}

/* ================================================================ run of show
   A room x time grid. The current implementation builds it from a
   repeating-linear-gradient keyed off `--slot-height`, a variable nothing ever
   sets (styles.css:6628), so it falls back to its inline default. */
.cvr-ros { display: grid; grid-template-columns: 62px minmax(0, 1fr); border: 1px solid var(--cv-border); border-radius: var(--cv-radius-lg); overflow: hidden; }
.cvr-ros-times { background: var(--cv-bg-raised); border-right: 1px solid var(--cv-border); }
.cvr-ros-time {
  height: 44px; padding: 2px var(--cv-space-2) 0;
  border-bottom: 1px solid var(--cv-border-subtle);
  font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--cv-text-2xs); color: var(--cv-text-muted);
  text-align: right;
}
.cvr-ros-body { position: relative; overflow-x: auto; }
.cvr-ros-rooms { display: grid; position: sticky; top: 0; z-index: var(--cv-z-sticky); background: var(--cv-bg-raised); border-bottom: 1px solid var(--cv-border); }
.cvr-ros-room {
  padding: var(--cv-space-1) var(--cv-space-2);
  border-right: 1px solid var(--cv-border-subtle);
  font-size: 9px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase; color: var(--cv-text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cvr-ros-cols { display: grid; position: relative; }
.cvr-ros-col { border-right: 1px solid var(--cv-border-subtle); position: relative; }
.cvr-ros-slot { height: 44px; border-bottom: 1px solid var(--cv-border-subtle); }

/* A <button>, so it needs colour, font and alignment stated — a UA button
   inherits none of them, and an unset `color` renders the session title in
   near-black on a dark timeline. */
.cvr-ev {
  position: absolute;
  padding: 3px var(--cv-space-2);
  border: none; border-left: 3px solid var(--cv-accent);
  border-radius: var(--cv-radius-sm);
  background: var(--cv-accent-wash);
  color: var(--cv-text); font: inherit; font-size: var(--cv-text-2xs);
  text-align: left; display: grid; align-content: start; gap: 1px;
  overflow: hidden; cursor: pointer;
}
.cvr-ev:hover { background: var(--cv-bg-active); }
.cvr-ev b { font-weight: var(--cv-weight-medium); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* On the accent wash, muted drops to ~4.2:1 at 9px. The speaker name is the
   second thing an operator reads off a block. */
.cvr-ev span {
  color: var(--cv-text-secondary); font-size: 9px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cvr-ev span.cvr-ev-t { font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums; }
/* A block shorter than two lines shows the title only — the time is already
   readable off the gutter, the title is not recoverable from anywhere else. */
.cvr-ev[data-short="true"] span { display: none; }
.cvr-ev[data-state="live"] { background: var(--cv-danger-wash); border-left-color: var(--cv-danger); }
.cvr-ev[data-state="done"] { opacity: 0.5; }

/* The now-line. The single most useful thing on a run-of-show during a show. */
.cvr-now { position: absolute; left: 0; right: 0; height: 2px; background: var(--cv-danger); z-index: 4; pointer-events: none; }
.cvr-now::before {
  content: "NOW"; position: absolute; left: 0; top: -8px;
  padding: 0 3px; border-radius: var(--cv-radius-sm);
  background: var(--cv-danger-solid); color: var(--cv-on-danger);
  font-size: 8px; font-weight: var(--cv-weight-semibold); letter-spacing: var(--cv-tracking-caps);
}

/* ============================================ ROS timeline (prototype re-skin)
   The live ROS render (renderROSTimeline) keeps its own `.ros-timeline-*`
   structure + per-second clock; this only restyles the APPEARANCE to the
   prototype's cvr-ros language (loaded after styles.css so these win). No JS
   changes — the render's inline top/height/left/width positioning is untouched.
   ---------------------------------------------------------------------------- */
.ros-timeline-container { border: 1px solid var(--cv-border); border-radius: var(--cv-radius-lg); overflow: hidden; background: var(--cv-bg-surface); }
.ros-timeline-header { padding: var(--cv-space-2); border-bottom: 1px solid var(--cv-border); background: var(--cv-bg-raised); }
.ros-timeline-header-bar { border-bottom: 1px solid var(--cv-border); background: var(--cv-bg-raised); }
.ros-timeline-corner {
  background: var(--cv-bg-raised); border-right: 1px solid var(--cv-border);
  font-size: 9px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase; color: var(--cv-text-secondary);
}
.ros-timeline-room {
  border-right: 1px solid var(--cv-border-subtle);
  font-size: 9px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase; color: var(--cv-text-secondary);
}
.ros-timeline-times, .ros-timeline-sidebar { background: var(--cv-bg-raised); border-right: 1px solid var(--cv-border); }
.ros-timeline-time {
  border-bottom: 1px solid var(--cv-border-subtle);
  font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--cv-text-2xs); color: var(--cv-text-muted); text-align: right;
}
.ros-timeline-column { border-right: 1px solid var(--cv-border-subtle); }
/* Event blocks — the visual heart of a run-of-show. Colour/border/type only;
   the render sets top/height/left/width inline and those are left alone. */
.ros-event {
  border: none; border-left: 3px solid var(--cv-accent);
  border-radius: var(--cv-radius-sm);
  background: var(--cv-accent-wash); color: var(--cv-text);
  overflow: hidden;
}
.ros-event:hover { background: var(--cv-bg-active); }
.ros-event.ros-event-live { background: var(--cv-danger-wash); border-left-color: var(--cv-danger); }
/* The now-line: the single most useful thing during a live show. */
.ros-now-line { background: var(--cv-danger); height: 2px; z-index: 4; }
.ros-now-line::before {
  content: "NOW"; position: absolute; left: 0; top: -8px;
  padding: 0 3px; border-radius: var(--cv-radius-sm);
  background: var(--cv-danger-solid); color: var(--cv-on-danger);
  font-size: 8px; font-weight: var(--cv-weight-semibold); letter-spacing: var(--cv-tracking-caps);
}
.ros-timeline-time.ros-time-now { color: var(--cv-danger); font-weight: var(--cv-weight-semibold); }

/* ============================================ ROS timeline — readability pass
   Tighter time gutter, room columns that fill the width (no dead space on the
   right), flat event blocks with one ellipsised field per line so the time no
   longer clips off the bottom, and a calmer LIVE treatment. Layout/positioning
   stays inline in the render; this is appearance only (paired with a computed
   time-range + a data-short flag added in renderROSTimeline/-EventsVertical).
   ---------------------------------------------------------------------------- */
/* Narrower time gutter — "12pm"-width labels never needed 140px. Corner and
   sidebar must match so the header room strip stays aligned with the columns. */
.ros-timeline-corner, .ros-timeline-sidebar { width: 64px; }
.ros-timeline-time { justify-content: flex-end; padding: 0 var(--cv-space-2) 0 var(--cv-space-1); }

/* Room columns flex to fill the available width; fall back to a 150px min
   (horizontal scroll) only when there are too many rooms to fit. */
.ros-timeline-rooms, .ros-timeline-columns { width: 100%; }
.ros-timeline-room, .ros-timeline-column { flex: 1 1 0; min-width: 150px; }

/* Header controls left-aligned — centered day buttons read as unmoored. */
.ros-timeline-nav, .ros-day-buttons-container { justify-content: flex-start; }

/* Event blocks — flat (no drop shadow / hover lift), tight padding. The inline
   colour wash + left border set by the render are preserved. */
.ros-event { box-shadow: none; padding: 1px var(--cv-space-2); }
.ros-event:hover { transform: none; box-shadow: none; filter: brightness(1.12); z-index: 6; }
.ros-event-content { gap: 0; }
/* One field per line, ellipsised — the wrapping was what pushed the time out of
   the bottom of a 30-minute block and clipped it. */
.ros-event-line {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; word-wrap: normal;
  line-height: 1.25; font-size: var(--cv-text-2xs);
}
.ros-event-line:first-child { font-weight: var(--cv-weight-medium); color: var(--cv-text); }
.ros-event-line:not(:first-child) {
  font-size: 9px; color: var(--cv-text-secondary);
  font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums;
}
/* A block too short to fit a second line shows the title only, rather than a
   half-clipped time (data-short is set by renderROSEventsVertical). */
.ros-event[data-short="true"] .ros-event-line:not(:first-child) { display: none; }

/* LIVE — the green glow + whole-block pulse were noise; the coloured left border
   and the blinking pill already say "live". Keep it calm. */
.ros-event.ros-event-live { box-shadow: none; animation: none; }
.ros-event.ros-event-live .ros-event-content { padding-right: 28px; }
.ros-live-indicator {
  position: absolute; top: 2px; right: 2px;
  padding: 0 4px; border-radius: var(--cv-radius-sm);
  background: var(--cv-danger-solid); color: var(--cv-on-danger);
  font-size: 8px; font-weight: var(--cv-weight-semibold); letter-spacing: var(--cv-tracking-caps);
}

/* ==================================================================== sites
   One card per layout — its published site (matched by slug) is folded into the
   same card with a status badge + Open link, so layouts and published sites are
   no longer two redundant grids. */
.cvr-sites { display: grid; gap: var(--cv-space-2); grid-template-columns: repeat(auto-fill, minmax(208px, 1fr)); }
.cvr-site {
  display: flex; flex-direction: column;
  border: 1px solid var(--cv-border); border-radius: var(--cv-radius-lg);
  background: var(--cv-bg-surface); overflow: hidden;
}
.cvr-site:hover { border-color: var(--cv-border-strong); }
.cvr-site-canvas {
  height: 88px; position: relative;
  background: var(--cv-bg-inset);
  border-bottom: 1px solid var(--cv-border-subtle);
  display: grid; place-items: center;
  color: var(--cv-text-disabled);
}
/* Transparent-background layouts are for OBS browser sources; show the
   checkerboard so "transparent" is not mistaken for "black". */
.cvr-site-canvas[data-bg="transparent"] {
  background-image:
    linear-gradient(45deg, var(--cv-bg-raised) 25%, transparent 25%),
    linear-gradient(-45deg, var(--cv-bg-raised) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--cv-bg-raised) 75%),
    linear-gradient(-45deg, transparent 75%, var(--cv-bg-raised) 75%);
  background-size: 12px 12px;
  background-position: 0 0, 0 6px, 6px -6px, -6px 0;
}
/* Layouts are not all 16:9. A portrait room sign shown in a landscape thumbnail
   tells the operator the wrong thing about what OBS will receive, so the card
   draws the real frame inside a fixed-height well. */
.cvr-site-frame {
  height: 74%; width: auto; max-width: 84%;
  border: 1px dashed var(--cv-border-strong);
  border-radius: 2px;
  display: grid; place-items: center;
}

.cvr-site-meta { padding: var(--cv-space-2); display: grid; gap: 2px; min-width: 0; }
.cvr-site-name { font-size: var(--cv-text-xs); font-weight: var(--cv-weight-medium); }
.cvr-site-sub { font-size: var(--cv-text-2xs); color: var(--cv-text-muted); display: flex; gap: var(--cv-space-2); }
.cvr-site-acts { display: flex; gap: 2px; padding: var(--cv-space-1) var(--cv-space-2); border-top: 1px solid var(--cv-border-subtle); }

/* Published / Draft status badge (merged layout+site card). */
.cvr-site-badge {
  display: inline-flex; align-items: center; gap: 3px; flex: none;
  padding: 0 5px; border-radius: var(--cv-radius-full);
  font-size: 8px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase;
  border: 1px solid var(--cv-border);
}
.cvr-site-badge .cv-i { width: 9px; height: 9px; }
.cvr-site-badge[data-s="pub"] { color: var(--cv-success); background: var(--cv-success-wash); border-color: var(--cv-success-edge); }
.cvr-site-badge[data-s="draft"] { color: var(--cv-text-muted); background: var(--cv-bg-raised); }
