/* ---------------------------------------------------------------------------
   Temple-side Real-time / Time-shift GUI prototype
   Dark theme matches the Shinnyo-en Miro board mockups: near-black
   background, gray buttons, green accent for active/success states, red for
   secret/customized items.
--------------------------------------------------------------------------- */

/* Vendored (not CDN), same pattern as js/vendor/dash.all.min.js -- a real
   7-segment/LED face for the header clock (2026-07-30), matching TVU
   Search's own real header clock. SIL Open Font License 1.1, see
   js/vendor/fonts/DSEG-LICENSE.txt. Only the one weight actually used
   (Bold) is vendored, not the full DSEG family. */
@font-face {
  font-family: 'DSEG7Classic';
  src: url('../js/vendor/fonts/DSEG7Classic-Bold.woff2') format('woff2');
  font-weight: bold;
  font-style: normal;
  font-display: swap;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: #000;
  font-family: "Hiragino Kaku Gothic ProN", "Yu Gothic", "Meiryo", sans-serif;
}

/* The app is fixed at exactly 1920x1080 (the hard no-scroll constraint from
   the board) and scaled to fit whatever window it's opened in for review --
   the scaling is a dev-preview convenience only, it does not change the
   actual 1920x1080 layout underneath. See fitAppToWindow() in app.js. */
.viewport-wrapper {
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.app {
  width: 1920px;
  height: 1080px;
  flex-shrink: 0;
  background: #0d0d0d;
  color: #e5e5e5;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 0 60px rgba(0, 0, 0, 0.8);
}

/* Hides the initial mock render while a stored session is being confirmed
   on page load, so a logged-in refresh never flashes mock content before
   swapping to live -- see the DOMContentLoaded handler in js/app.js. */
.app.restoring-session {
  visibility: hidden;
}

button {
  font-family: inherit;
  cursor: pointer;
}

/* --- Tab bar --------------------------------------------------------- */

.tab-bar {
  display: flex;
  gap: 12px;
  padding: 20px 32px 0;
  height: 76px;
  align-items: flex-end;
  border-bottom: 3px solid #333;
  flex-shrink: 0;
}

.header-logo {
  height: 44px;
  width: auto;
  align-self: center;
  margin-right: 8px;
  /* .tab-bar's padding is asymmetric (20px top, 0 bottom), so centering
     within its content box alone sits a bit low relative to the full
     header -- nudge up by half that difference to true-center it. */
  transform: translateY(-10px);
  /* Same white knockout as the preview-standby logo -- see .tvu-logo img. */
  filter: brightness(0) invert(1);
}

.tab-bar .tab {
  width: 240px;
  padding: 14px 0;
  font-size: 22px;
  text-align: center;
  border: 2px solid #666;
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  background: #1a1a1a;
  color: #ccc;
}

.tab-bar .tab.active {
  background: #adf0c7;
  color: #1a1a1a;
  border-color: #2dc75c;
  font-weight: bold;
}

.mode-badge {
  margin-bottom: 14px;
  padding: 8px 18px;
  border-radius: 999px;
  border: 2px solid #666;
  background: #2a2a2a;
  color: #ccc;
  font-size: 13px;
  font-weight: bold;
  letter-spacing: 0.05em;
  /* Never wrap or get squeezed by the flex row -- a long account name/email
     must always render on one line in full, even if it pushes wider than
     the tabs' combined width. */
  white-space: nowrap;
  flex-shrink: 0;
  max-width: none;
}

.mode-badge.live {
  background: #d9432e;
  border-color: #ff8080;
  color: #fff;
}

/* Header clock (2026-07-30), matching TVU Search's own real header clock --
   shared across G-01/G-02 since it lives in the persistent .tab-bar, not
   inside either tab's own panel. Always shows the real system time,
   regardless of MOCK/LIVE mode or which day G-02's Timeline is browsing. */
.header-clock {
  margin-left: auto;
  margin-bottom: 14px;
  padding: 6px 14px;
  border-radius: 6px;
  border: 1px solid #664400;
  background: #1a1408;
  color: #ffb020;
  white-space: nowrap;
  flex-shrink: 0;
}

/* HH:MM:SS in the real vendored DSEG7 LED face (see the @font-face above)
   -- letters aren't this font's job, see .clock-suffix below. */
.header-clock .clock-digits {
  font-family: 'DSEG7Classic', 'Courier New', monospace;
  font-size: 30px;
  letter-spacing: 0.03em;
}

/* AM/PM + JST -- DSEG7 has no real letterform coverage for these, so they
   stay in a normal readable font, smaller and dimmer than the digits so
   they read as a secondary detail. */
.header-clock .clock-suffix {
  font-family: 'SF Mono', 'Consolas', 'DejaVu Sans Mono', 'Courier New', monospace;
  font-size: 14px;
  font-weight: bold;
  color: #c98a1f;
}

.live-error-banner {
  position: absolute;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  background: #d9432e;
  color: #fff;
  padding: 12px 24px;
  border-radius: 6px;
  font-size: 14px;
  z-index: 200;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
}

/* --- Tab content ------------------------------------------------------ */

.tab-content {
  flex: 1;
  position: relative;
  padding: 24px 32px;
  overflow: hidden;
  min-height: 0;
}

.tab-panel {
  display: none;
  height: 100%;
}

.tab-panel.active {
  display: flex;
  flex-direction: column;
}

/* --- Shared: receiver preview thumbnail -------------------------------- */

/* Wraps .preview-thumb so it can stay locked to 16:9 and letterbox with
   black bars, regardless of how many receiver columns are on screen (2026-
   07-27 fix). .receiver-column always stretches to the full row height
   whether there are 1, 2, or 3 columns, but a WIDER column (fewer
   receivers) would otherwise make aspect-ratio-driven .preview-thumb want
   to be much taller too, forcing it to distort to fit.
   .preview-thumb itself has no content that contributes to intrinsic size
   (every child -- video, colorbars, logo, labels -- is position:absolute),
   so neither max-width/max-height:100% (collapses to ~0, no intrinsic size
   to constrain) nor `inset:0; margin:auto` (always matches the frame's
   width, then overflows vertically instead of shrinking) actually fits-and-
   centers it -- both confirmed wrong by inspecting the real rendered box.
   Container query units are the reliable fix: `container-type: size` lets
   .preview-thumb size itself from the frame's own width(cqw)/height(cqh),
   and `min()` picks whichever axis is the binding constraint -- bars on the
   sides when width is tighter (fewer columns), top/bottom when height is
   tighter (more columns). */
.preview-thumb-frame {
  flex: 1;
  min-height: 0;
  position: relative;
  background: #000;
  border-radius: 4px;
  overflow: hidden;
  container-type: size;
}

.preview-thumb {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(100cqw, calc(100cqh * 16 / 9));
  height: min(100cqh, calc(100cqw * 9 / 16));
  background: #111;
  border: 2px dashed #555;
  border-radius: 4px;
  overflow: hidden;
}

.preview-thumb .live-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  background: #d9432e;
  color: #fff;
  font-size: 13px;
  font-weight: bold;
  padding: 2px 10px;
  border-radius: 3px;
  z-index: 2;
}

.preview-thumb .tvu-logo {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 70px;
  height: 70px;
  z-index: 2;
}

/* White knockout (2026-07-27) -- the logo's own green was hard to see
   against the dark standby colorbars. Applies to every .tvu-logo img
   (the main preview thumb, the small mini-preview, and G-02's preview
   frame) since they all nest an <img> under a .tvu-logo element. */
.tvu-logo img {
  filter: brightness(0) invert(1);
}

.preview-thumb .tvu-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.preview-thumb .colorbars {
  position: absolute;
  inset: 0;
  display: flex;
  z-index: 0;
}

.preview-thumb .colorbars span {
  flex: 1;
}

.preview-thumb .colorbars span:nth-child(1) { background: #c0c0c0; }
.preview-thumb .colorbars span:nth-child(2) { background: #c4c400; }
.preview-thumb .colorbars span:nth-child(3) { background: #00c4c4; }
.preview-thumb .colorbars span:nth-child(4) { background: #00a000; }
.preview-thumb .colorbars span:nth-child(5) { background: #a000a0; }
.preview-thumb .colorbars span:nth-child(6) { background: #a00000; }
.preview-thumb .colorbars span:nth-child(7) { background: #0000a0; }
.preview-thumb .colorbars span:nth-child(8) { background: #1a1a1a; }

.preview-thumb .receiver-id-label {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(0, 0, 0, 0.65);
  color: #ddd;
  font-size: 11px;
  padding: 3px 6px;
  z-index: 2;
}

/* Real WebRTC decode preview (G-01 live mode). Hidden until
   webrtcPreview.js's ontrack fires and adds ".active" -- a stalled/failed
   connection just leaves the colorbars mock visible underneath (z-index 0),
   never a broken/frozen video element. */
.receiver-preview-video {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 1;
}

.receiver-preview-video.active {
  display: block;
}

.preview-thumb:has(.receiver-preview-video.active) .colorbars,
.preview-thumb:has(.receiver-preview-video.active) .tvu-logo {
  display: none;
}

/* Receiver-offline treatment (G-01 live mode, 2026-07-28) -- CC's own
   queryRlist status ('0'), not the WebRTC preview's own connection state:
   a receiver can be offline well before any preview attempt happens.
   Blacks out everything else in the thumb (colorbars/logo/video/badge/
   meter/mute-toggle) so the operator sees "Offline" only, not a stale mock
   or a frozen/blank video underneath it. */
.offline-overlay {
  display: none;
}

.preview-thumb.receiver-offline .offline-overlay {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
  color: #fff;
  font-size: 30px;
  font-weight: bold;
}

.preview-thumb.receiver-offline .colorbars,
.preview-thumb.receiver-offline .tvu-logo,
.preview-thumb.receiver-offline .receiver-preview-video,
.preview-thumb.receiver-offline .live-badge,
.preview-thumb.receiver-offline .audio-meter,
.preview-thumb.receiver-offline .mute-toggle {
  display: none;
}

/* Real per-channel audio level meter (G-01 live mode, 2026-07-27) --
   deliberately independent of .receiver-preview-video: it reflects the
   receiver's own real audio state via pageRWebsocket regardless of whether
   the WebRTC preview itself is rendering (see FB-10265/ISSUES.md), so it is
   NOT gated behind `.receiver-preview-video.active` the way the mute toggle
   below is. */
.preview-thumb .audio-meter {
  position: absolute;
  /* Clears the mute toggle (top-right) above and the receiver-id-label
     (full-width strip along the very bottom) below. */
  top: 36px;
  bottom: 28px;
  right: 8px;
  display: flex;
  align-items: flex-end;
  gap: 2px;
  z-index: 3;
  pointer-events: none;
}

.preview-thumb .audio-meter .meter-bar {
  width: 4px;
  background: #2dc75c;
  border-radius: 1px;
  transition: height 0.15s linear;
}

/* Mute/unmute toggle -- always rendered, only shown once a real WebRTC
   preview is actually active (muting has nothing to control before that). */
.mute-toggle {
  display: none;
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 3;
  background: rgba(0, 0, 0, 0.55);
  border: none;
  border-radius: 4px;
  color: #fff;
  font-size: 16px;
  line-height: 1;
  padding: 4px 6px;
}

.preview-thumb:has(.receiver-preview-video.active) .mute-toggle {
  display: block;
}

/* --- G-01: Grid receiving control -------------------------------------- */

.receiver-grid {
  display: flex;
  gap: 24px;
  height: 100%;
}

.receiver-column {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  border: 2px dashed transparent;
  border-radius: 12px;
  padding: 10px;
  position: relative;
}

.receiver-column.active-target {
  border-color: #fff;
}

.receiver-label-row {
  display: flex;
  gap: 8px;
}

.receiver-label {
  flex: 1;
  text-align: center;
  background: #3a3a3a;
  border: 2px solid #888;
  border-radius: 6px;
  padding: 8px;
  font-size: 18px;
}

.receiver-label-row .btn-secondary {
  flex-shrink: 0;
}

.current-source-row {
  display: flex;
  gap: 8px;
}

.current-source-label {
  flex: 1;
  background: #222;
  border: 1px solid #555;
  border-radius: 6px;
  padding: 10px;
  font-size: 15px;
  display: flex;
  align-items: center;
}

.btn-stop, .btn-take {
  border: none;
  border-radius: 6px;
  padding: 0 20px;
  font-size: 15px;
  font-weight: bold;
}

.btn-stop {
  background: #d9432e;
  color: #fff;
}

.btn-take {
  background: #2dc75c;
  color: #1a1a1a;
}

.btn-take:disabled {
  background: #444;
  color: #888;
  cursor: not-allowed;
}

.source-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(4, 1fr);
  gap: 10px;
  /* Grows to fill the column's remaining height down to .destination-search
     instead of staying a fixed size and leaving dead space above it (the
     8 buttons' rows split that space evenly via grid-template-rows). */
  flex: 1;
}

.source-btn {
  padding: 14px 8px;
  border-radius: 6px;
  border: 1px solid #666;
  background: #3a3a3a;
  color: #ddd;
  font-size: 21px;
  line-height: 1.4;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.source-btn.selected {
  background: #adf0c7;
  color: #1a1a1a;
  border-color: #2dc75c;
  font-weight: bold;
}

/* Resolved real source name, forced onto its own block row (2026-07-29) so
   the event badge below always starts on a fresh line, even when this name
   itself is long enough to wrap across 2 lines on its own. */
.source-name-row {
  display: block;
}

/* Currently-recording TVU Search event title, shown under a resolved real
   source name (2026-07-29, see pollOngoingEvents in js/app.js). Customer
   asked for this to stand out as important information -- a solid
   high-visibility badge (not just colored text) so it reads clearly
   whether the button is in its default or .selected (light green) state. */
.source-event-name {
  display: block;
  width: fit-content;
  margin: 4px auto 0;
  font-size: 13px;
  font-weight: bold;
  color: #fff;
  background: #7a5c12;
  padding: 2px 10px;
  border-radius: 4px;
}

.source-btn.preset {
  color: #ff8080;
  border-color: #ff8080;
}

.source-btn.preset.selected {
  background: #ffd6d6;
  color: #7a1f1f;
  border-color: #d9432e;
}

.source-btn.preset.unset {
  color: #888;
  border-color: #666;
  cursor: not-allowed;
}

/* Secret (password-gated) HQ sources -- blue accent per the SOW's original
   mockup, so they read as "different" at a glance, not just when the
   password prompt appears. */
.source-btn.secret {
  color: #7ab8ff;
  border-color: #7ab8ff;
}

.source-btn.secret.selected {
  background: #d6e8ff;
  color: #1a3a5c;
  border-color: #2e6fd9;
}

.destination-search {
  margin-top: auto;
  position: relative;
}

.search-toggle {
  width: 100%;
  padding: 10px;
  background: #2a2a2a;
  border: 1px solid #666;
  border-radius: 6px;
  color: #ddd;
  font-size: 14px;
}

/* G01_ONLY_HQ1_ENABLED (js/app.js, temporary/customer-requested) grays out
   whichever G-01 controls it disables -- matches the existing disabled look
   already used elsewhere (.guide-toolbar .btn-secondary:disabled). */
.source-btn:disabled,
.search-toggle:disabled,
.btn-secondary:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.search-panel {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 48px;
  z-index: 30;
  background: #2a2a2a;
  border: 1px solid #666;
  border-radius: 6px;
  padding: 10px;
  max-height: 640px;
  box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.6);
}

.search-panel.expanded {
  display: flex;
  flex-direction: column;
}

.search-input {
  -webkit-appearance: none;
  appearance: none;
  padding: 8px;
  margin-bottom: 8px;
  background: #1a1a1a;
  color: #fff;
  border: 1px solid #666;
  border-radius: 4px;
  font-size: 13px;
}

.search-results {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
}

.search-result-item {
  padding: 8px;
  font-size: 14px;
  border-bottom: 1px solid #3a3a3a;
}

.search-result-item:hover {
  background: #3a3a3a;
}

.search-disabled-message {
  padding: 16px 8px;
  color: #888;
  font-size: 13px;
  text-align: center;
}

.source-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.source-item-name {
  flex: 1;
  cursor: pointer;
}

.source-item-actions {
  display: flex;
  gap: 4px;
  flex-shrink: 0;
}

.btn-preset-assign {
  padding: 4px 8px;
  border-radius: 4px;
  border: 1px solid #ff8080;
  background: #2a2a2a;
  color: #ff8080;
  font-size: 12px;
}

.btn-preset-assign:hover {
  background: #ff8080;
  color: #1a1a1a;
}

/* Row currently bound to this receiver's PRESET-1 and/or PRESET-2 --
   reuses the same red accent as .source-btn.preset(.selected) elsewhere in
   G-01 so "red = preset" reads consistently across the screen. */
.source-item.assigned-preset {
  background: #3a1f1f;
}

.btn-preset-assign.active {
  background: #d9432e;
  border-color: #d9432e;
  color: #fff;
  font-weight: bold;
}

.btn-preset-assign.active:hover {
  background: #ff8080;
}

/* --- G-02: Search time-shift playback control (program-guide style) ----- */

.timeshift-layout {
  display: flex;
  flex-direction: column;
  gap: 12px;
  height: 100%;
  min-height: 0;
}

.guide-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.guide-toolbar .event-filter-input {
  flex: 0 0 260px;
}

.guide-toolbar .timeline-date-label {
  flex: 1;
  text-align: center;
  font-size: 14px;
  color: #ddd;
}

.guide-toolbar .btn-secondary:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Hour ruler shared across every channel row. Left-padded to line up with
   each row's track area (which starts after the fixed-width channel label
   column) -- see CHANNEL_LABEL_WIDTH in app.js. */
.guide-hours {
  position: relative;
  height: 18px;
  margin-left: 168px;
  font-size: 11px;
  color: #888;
  flex-shrink: 0;
}

.guide-hours span {
  position: absolute;
  transform: translateX(-50%);
}

/* Current-time marker (2026-07-30) -- rendered once in the hour ruler and
   once per channel row's own track (see renderChannelGuide/channelRowHtml),
   since .guide-hours and .guide-rows reach their shared 168px label offset
   through two different mechanisms and there's no single shared coordinate
   box to draw one continuous overlay across both. */
.now-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #2dc75c;
  z-index: 5;
  pointer-events: none;
}

.guide-rows {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  min-height: 0;
}

.guide-row {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
  height: 56px;
}

.guide-row-label {
  width: 160px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  background: #1e1e1e;
  border: 1px solid #444;
  border-radius: 6px;
  font-size: 13px;
  color: #ccc;
  padding: 4px;
}

.guide-row-track {
  position: relative;
  flex: 1;
  background: #0d0d0d;
  border: 1px solid #333;
  border-radius: 4px;
}

.guide-event-block {
  position: absolute;
  top: 4px;
  bottom: 4px;
  min-width: 8px;
  background: #3a6b48;
  border: 1px solid #2dc75c;
  border-radius: 3px;
  color: #cdeedb;
  font-size: 12px;
  overflow: hidden;
  white-space: nowrap;
  padding: 4px 8px;
  cursor: pointer;
}

.guide-event-block:hover {
  background: #46815a;
}

.guide-event-block.selected {
  background: #2dc75c;
  color: #1a1a1a;
  border-color: #adf0c7;
  font-weight: bold;
}

/* --- Inspector popup: opens on the right when a guide event is clicked -- */

.side-panel-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  justify-content: flex-end;
  z-index: 90;
}

.inspector-popup-panel {
  width: 680px;
  height: 100%;
  background: #161616;
  border-left: 2px solid #444;
  padding: 20px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: -12px 0 32px rgba(0, 0, 0, 0.6);
}

.inspector-popup-header {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 12px;
  flex-shrink: 0;
}

.btn-icon {
  background: none;
  border: none;
  color: #ccc;
  font-size: 22px;
  line-height: 1;
  padding: 0 4px;
}

.receiver-strip {
  display: flex;
  gap: 12px;
  margin-bottom: 6px;
  flex-shrink: 0;
}

.receiver-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px 4px 4px;
  border: 2px solid #555;
  border-radius: 8px;
  background: #1e1e1e;
  color: #ddd;
  font-size: 15px;
}

.receiver-chip.active {
  border-color: #2dc75c;
  background: #1c2b20;
}

.receiver-chip .mini-preview {
  width: 44px;
  height: 28px;
  background: #111;
  border-radius: 4px;
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
}

.tvu-logo.small {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 26px;
  height: 26px;
}

.tvu-logo.small img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.event-filter-input {
  -webkit-appearance: none;
  appearance: none;
  padding: 8px;
  background: #1a1a1a;
  border: 1px solid #666;
  color: #fff;
  border-radius: 4px;
  font-size: 13px;
}

.btn-secondary {
  padding: 8px 14px;
  background: #2a2a2a;
  border: 1px solid #666;
  color: #ddd;
  border-radius: 6px;
  font-size: 13px;
}

.rec-badge {
  color: #2dc75c;
  font-weight: bold;
  font-size: 14px;
}

.inspector-title {
  flex: 1;
  font-size: 18px;
}

.inspector-times {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  color: #999;
  margin-bottom: 10px;
}

.preview-frame {
  width: 100%;
  aspect-ratio: 16 / 9;
  flex-shrink: 0;
  position: relative;
  background: #000;
  border: 2px dashed #444;
  border-radius: 4px;
  margin-bottom: 12px;
  overflow: hidden;
}

.preview-frame .live-badge {
  position: absolute;
  top: 10px;
  left: 10px;
  background: #2dc75c;
  color: #1a1a1a;
  font-size: 13px;
  font-weight: bold;
  padding: 3px 12px;
  border-radius: 3px;
  z-index: 2;
}

.preview-frame .tvu-logo {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 2;
  gap: 4px;
}

.preview-frame .tvu-logo img {
  width: 90px;
  height: 90px;
  object-fit: contain;
}

.preview-frame .tvu-logo small {
  font-size: 11px;
  font-weight: normal;
  color: #9fe6b8;
}

.preview-frame .colorbars {
  position: absolute;
  inset: 0;
  display: flex;
}

.preview-frame .colorbars span {
  flex: 1;
}

.preview-frame .colorbars span:nth-child(1) { background: #c0c0c0; }
.preview-frame .colorbars span:nth-child(2) { background: #c4c400; }
.preview-frame .colorbars span:nth-child(3) { background: #00c4c4; }
.preview-frame .colorbars span:nth-child(4) { background: #00a000; }
.preview-frame .colorbars span:nth-child(5) { background: #a000a0; }
.preview-frame .colorbars span:nth-child(6) { background: #a00000; }
.preview-frame .colorbars span:nth-child(7) { background: #0000a0; }
.preview-frame .colorbars span:nth-child(8) { background: #1a1a1a; }

.preview-frame .receiver-id-label {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(0, 0, 0, 0.65);
  color: #ddd;
  font-size: 12px;
  padding: 4px 8px;
  z-index: 2;
}

/* Real TVU Search recording preview (G-02 live mode). Hidden until dash.js
   fires PLAYBACK_STARTED and adds ".active" -- a stalled/failed stream just
   leaves the colorbars mock visible underneath (z-index 0), never a
   broken/frozen video element. */
.inspector-preview-video {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 1;
}

.inspector-preview-video.active {
  display: block;
}

.preview-frame:has(.inspector-preview-video.active) .colorbars,
.preview-frame:has(.inspector-preview-video.active) .tvu-logo {
  display: none;
}

/* Selected receiver's own real-time preview (2026-07-29). A fixed height
   here squashed the real video out of its actual aspect ratio (found via a
   real-account screenshot, 2026-07-29) -- same 16:9 aspect-ratio as
   .preview-frame above keeps the picture correct; everything else in this
   panel was already shrunk (cont.10) to leave room for this at full width. */
.receiver-preview-frame {
  width: 100%;
  aspect-ratio: 16 / 9;
  flex-shrink: 0;
  position: relative;
  background: #000;
  border: 2px dashed #444;
  border-radius: 4px;
  margin-top: 12px;
  overflow: hidden;
}

.receiver-preview-frame .colorbars {
  position: absolute;
  inset: 0;
  display: flex;
}

.receiver-preview-frame .colorbars span {
  flex: 1;
}

.receiver-preview-frame .colorbars span:nth-child(1) { background: #c0c0c0; }
.receiver-preview-frame .colorbars span:nth-child(2) { background: #c4c400; }
.receiver-preview-frame .colorbars span:nth-child(3) { background: #00c4c4; }
.receiver-preview-frame .colorbars span:nth-child(4) { background: #00a000; }
.receiver-preview-frame .colorbars span:nth-child(5) { background: #a000a0; }
.receiver-preview-frame .colorbars span:nth-child(6) { background: #a00000; }
.receiver-preview-frame .colorbars span:nth-child(7) { background: #0000a0; }
.receiver-preview-frame .colorbars span:nth-child(8) { background: #1a1a1a; }

.receiver-preview-frame .receiver-id-label {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(0, 0, 0, 0.65);
  color: #ddd;
  font-size: 12px;
  padding: 4px 8px;
  z-index: 2;
}

.g02-receiver-preview-video {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 1;
}

.g02-receiver-preview-video.active {
  display: block;
}

.receiver-preview-frame:has(.g02-receiver-preview-video.active) .colorbars,
.receiver-preview-frame:has(.g02-receiver-preview-video.active) .tvu-logo {
  display: none;
}

.scrubber {
  height: 32px;
  background: #0d0d0d;
  border: 1px solid #333;
  border-radius: 4px;
  position: relative;
  margin-bottom: 6px;
  cursor: pointer;
  flex-shrink: 0;
}

.scrubber .playhead,
.scrubber .mark-in,
.scrubber .mark-out {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 3px;
}

.scrubber .playhead {
  background: #3498db;
}

.scrubber .mark-in,
.scrubber .mark-out {
  background: #f1c40f;
}

/* Mark-In/Mark-Out range highlight, matching TVU Search's own real Inspector
   (2026-07-29) -- same yellow hue as the mark-in/mark-out lines themselves,
   just translucent so the playhead (blue) stays visible on top of it. */
.scrubber .mark-range {
  position: absolute;
  top: 0;
  bottom: 0;
  background: rgba(241, 196, 15, 0.25);
}

.transport-row {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-bottom: 6px;
  flex-shrink: 0;
}

.transport-btn {
  background: #2a2a2a;
  border: 1px solid #555;
  border-radius: 6px;
  padding: 4px 9px;
  color: #ddd;
  font-size: 15px;
}

/* The center play/pause button (real Search parity, 2026-07-29) reads as
   the primary transport action -- same green accent already used for
   .live-badge/.playhead-time elsewhere in this panel. */
#play-pause-btn {
  background: #2dc75c;
  color: #1a1a1a;
  border-color: #2dc75c;
  font-weight: bold;
}

.playhead-time {
  text-align: center;
  color: #2dc75c;
  font-size: 15px;
  border: 1px solid #2dc75c;
  border-radius: 6px;
  padding: 3px 0;
  width: 150px;
  margin: 0 auto 8px;
  flex-shrink: 0;
}

.sdi-controls {
  display: flex;
  justify-content: center;
  gap: 20px;
  flex-shrink: 0;
  margin-bottom: 6px;
}

.pill-btn {
  padding: 6px 22px;
  border-radius: 999px;
  border: 1px solid #666;
  background: #3a3a3a;
  color: #ddd;
  font-size: 14px;
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 2px;
  min-width: 110px;
}

.pill-btn span {
  font-size: 11px;
  color: #aaa;
}

.pill-btn.active-state {
  background: #adf0c7;
  color: #1a1a1a;
  border-color: #2dc75c;
}

.pill-btn.active-state span {
  color: #1a1a1a;
}

/* Shrunk from the shared .btn-secondary default (2026-07-29) so the new
   receiver preview below it fits without the panel getting too tall --
   scoped to this id, not .btn-secondary itself, since that class is shared
   by many unrelated buttons elsewhere (Take/Stop/modals/etc.). */
#open-audio-mapping {
  padding: 5px 14px;
}

/* --- Shared modals ------------------------------------------------------ */

.hidden {
  display: none !important;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.password-modal-box {
  background: #1a1a1a;
  border: 2px solid #fff;
  border-radius: 8px;
  padding: 32px;
  text-align: center;
  width: 480px;
}

.password-message {
  font-size: 20px;
  margin: 0 0 16px;
}

.password-input {
  width: 100%;
  padding: 10px;
  background: #0d0d0d;
  border: 1px solid #666;
  color: #fff;
  border-radius: 4px;
  text-align: center;
  font-size: 16px;
}

.password-error {
  color: #ff5555;
  font-size: 13px;
  min-height: 18px;
  margin: 8px 0 0;
}

.modal-actions {
  display: flex;
  justify-content: center;
  gap: 16px;
  margin-top: 20px;
}

.btn-primary {
  background: #2dc75c;
  color: #1a1a1a;
  border: none;
  border-radius: 6px;
  padding: 10px 24px;
  font-weight: bold;
  font-size: 14px;
}

.audio-modal-box {
  background: #1a1a1a;
  border: 2px solid #fff;
  border-radius: 8px;
  padding: 24px;
  width: 660px;
}

.audio-modal-title {
  margin: 0 0 4px;
  font-size: 20px;
}

.audio-modal-subtitle {
  margin: 0 0 16px;
  color: #999;
  font-size: 13px;
}

.audio-matrix-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.webhook-log-modal-box {
  background: #1a1a1a;
  border: 2px solid #fff;
  border-radius: 8px;
  padding: 24px;
  width: 760px;
}

.webhook-log-list {
  height: 420px;
  overflow-y: auto;
  border: 1px solid #444;
  border-radius: 6px;
  padding: 8px;
  background: #0d0d0d;
}

.webhook-log-empty {
  color: #888;
  font-size: 13px;
  text-align: center;
  padding: 16px;
}

.webhook-log-entry {
  border-bottom: 1px solid #333;
  padding: 8px 4px;
}

.webhook-log-entry:last-child {
  border-bottom: none;
}

.webhook-log-entry-time {
  color: #7ab8ff;
  font-size: 12px;
  margin-bottom: 4px;
}

.webhook-log-entry-body {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-all;
  font-size: 12px;
  color: #ddd;
}

.axis-label-top {
  font-size: 12px;
  color: #999;
  margin-bottom: 4px;
  margin-left: 36px;
}

.matrix-body {
  display: flex;
  gap: 8px;
  align-items: flex-start;
}

.axis-label-left {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  font-size: 12px;
  color: #999;
  padding-top: 20px;
}

.audio-matrix {
  border-collapse: collapse;
}

.audio-matrix th {
  color: #999;
  font-size: 11px;
  font-weight: normal;
  padding: 2px 4px;
}

.audio-matrix td {
  padding: 2px;
}

.matrix-cell {
  width: 24px;
  height: 20px;
  border: 1px solid #444;
  background: #2a2a2a;
  border-radius: 2px;
  padding: 0;
}

.matrix-cell.on {
  background: #2dc75c;
  border-color: #adf0c7;
}
