Files
ersatztv/web/src/shell.css
T
timothyandClaude Opus 5 e605e4006a fix(651): review round 7 — treat "no usable token" as one class, not three values
HIGH: the fail-closed gate rejected `null` but not the adjacent values. `Headers.get('ETag')`
returns `''` for an empty or whitespace-only header, which PASSED the gate and produced an
editable draft; `updateRerunCollection`'s `ifMatch ? … : undefined` then dropped the empty
string as falsy and sent no `If-Match`, silently overwriting a collaborator — the exact class
the gate exists to make unreachable, reached through the value next door. Absent, empty and
whitespace are now one case ("no usable concurrency token"), normalized by a single
`usableEtag` helper that returns the TRIMMED token or null, so `etagRef` can only ever hold
something that will actually be sent. Tested across four blank shapes asserting zero PUTs are
reachable, plus a padded ETag that must be trimmed and USED rather than dropped.

MEDIUM: the deadline abandoned the wait without cancelling the work, so each Retry stacked
another live connection. It now aborts via an AbortSignal (threaded through
`getRerunCollectionWithMeta`) AND clears its timer on settlement and unmount. Both halves are
kept deliberately: aborting cancels the work, while the rejected race stops the UI waiting
even if the abort never propagates — cancellation and giving-up are not the same guarantee,
which the late-settlement test proves by using a stub that ignores its signal.

MEDIUM: `Number.isFinite` accepted ids the API cannot bind — `1.5` and values outside int32
rendered, committed through `onSelect`, and would fail server-side on `selectedId`. Validated
as an int32 integer.

MEDIUM: a malformed or failed page was reported as "No matches", telling the user the library
is empty when the request actually failed and giving no hint that reopening retries. Failures
now surface as a distinct alert.

MEDIUM: `spa-conventions.md` still mandated the deleted "never let a refresh clear an id it
failed to name" guard and said "the client guard stays" — contradicting the initialize-once
bullet 20 lines below it. Rewritten to state that the guard is gone and must not be rebuilt,
with the reason (it only ever preserved a list-seeded value that is null in production).
Grepping the DELETED TERMS across all docs — the lesson from round 6's stale `rule:` — also
caught two stale `signals:` tokens on the record that the rule fix had missed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 04:25:14 +02:00

4678 lines
99 KiB
CSS

/* Base reset on <html>/<body>: the browser-default body margin (8px) frames every
full-viewport layout — the app shell AND the shell-less .ctv-auth-page boot pages, both
min-height:100vh — exposing a light border on all sides (#373). Strip it and paint the app
surface so any residual gap/overscroll stays dark. Matches the design-system convention
(design-system/components/forms/forms.card.html, .../chicorytv-admin/*.html). */
html,
body {
margin: 0;
}
/* Base typography on <body>: Dialog (and any overlay) portals into document.body, outside
.ctv-app-shell — without this, portaled UI falls back to the browser default font. */
body {
background: var(--surface-app);
font-family: var(--font-sans, system-ui, sans-serif);
font-size: var(--text-sm, 13px);
color: var(--text-primary, #f0ebe4);
}
.ctv-app-shell {
min-height: 100vh;
display: grid;
grid-template-columns: var(--sidebar-w, 232px) minmax(0, 1fr);
overflow: hidden;
background: var(--surface-app);
color: var(--text-primary);
font-family: var(--font-sans, system-ui, sans-serif);
font-size: var(--text-sm, 13px);
/* Animate the sidebar collapse/expand (#396). Only the grid track width transitions so the
content reflows smoothly without a layout jump. */
transition: grid-template-columns var(--dur-base, 140ms) var(--ease-standard);
}
/* Collapsed sidebar = the 60px icon rail (#396). Narrowing the tracked custom property drives the
grid-template-columns transition above. */
.ctv-app-shell-collapsed {
--sidebar-w: 60px;
}
@media (prefers-reduced-motion: reduce) {
.ctv-app-shell {
transition: none;
}
}
.ctv-sidebar {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
border-right: 1px solid var(--border-hairline);
background: var(--surface-card);
}
.ctv-brand {
height: var(--topbar-h, 52px);
display: flex;
flex: 0 0 auto;
align-items: center;
gap: var(--space-5, 10px);
padding: 0 14px;
border-bottom: 1px solid var(--border-hairline);
}
.ctv-brand img {
width: 24px;
height: 24px;
}
.ctv-brand-wordmark {
color: var(--text-primary);
font-size: var(--text-lg, 16px);
font-weight: var(--weight-semibold, 600);
line-height: 1;
}
.ctv-brand-wordmark span {
color: var(--action-primary);
}
.ctv-nav {
flex: 1;
overflow: auto;
padding: var(--space-4, 8px) var(--space-4, 8px) var(--space-8, 20px);
}
.ctv-sidebar-health {
min-height: 58px;
display: flex;
flex: 0 0 auto;
align-items: center;
justify-content: space-between;
gap: var(--space-5, 10px);
padding: var(--space-6, 12px) 14px;
border-top: 1px solid var(--border-hairline);
}
.ctv-sidebar-health div {
min-width: 0;
display: grid;
gap: var(--space-1, 2px);
}
.ctv-sidebar-health span {
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
}
.ctv-sidebar-health code {
color: var(--text-secondary);
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--text-2xs, 11px);
}
/* Sidebar collapse toggle in the brand header (#396). */
.ctv-sidebar-toggle {
margin-left: auto;
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: 0;
border-radius: var(--radius-sm, 5px);
background: transparent;
color: var(--text-disabled);
cursor: pointer;
transition:
background var(--dur-fast) var(--ease-standard),
color var(--dur-fast) var(--ease-standard);
}
.ctv-sidebar-toggle:hover {
background: var(--ctv-accent-soft);
color: var(--text-primary);
}
.ctv-sidebar-toggle:focus-visible {
outline: none;
box-shadow: var(--ring-focus);
}
/* Hairline divider shown between nav groups in the collapsed rail (accordions don't apply
there, so the group label/chevron is replaced by a plain separator — #396). */
.ctv-nav-divider {
height: 1px;
margin: var(--space-4, 8px) var(--space-4, 8px);
background: var(--border-hairline);
}
/* ---- Collapsed (icon-rail) sidebar overrides (#396) ---- */
.ctv-app-shell-collapsed .ctv-brand {
padding: 0;
justify-content: center;
}
.ctv-app-shell-collapsed .ctv-brand img,
.ctv-app-shell-collapsed .ctv-brand-wordmark {
display: none;
}
.ctv-app-shell-collapsed .ctv-sidebar-toggle {
margin-left: 0;
}
/* Footer: hide the version text block and the health label, keep the status dot centered. */
.ctv-app-shell-collapsed .ctv-sidebar-health {
justify-content: center;
padding: var(--space-6, 12px) 0;
}
.ctv-app-shell-collapsed .ctv-sidebar-health > div {
display: none;
}
.ctv-app-shell-collapsed .ctv-sidebar-health .ctv-status-dot > span:last-child {
display: none;
}
.ctv-shell-body {
min-width: 0;
height: 100vh;
display: grid;
grid-template-rows: var(--topbar-h, 52px) minmax(0, 1fr);
overflow: hidden;
}
.ctv-topbar {
min-width: 0;
display: flex;
align-items: center;
gap: var(--space-7, 16px);
padding: 0 20px;
border-bottom: 1px solid var(--border-hairline);
background: var(--surface-app);
}
.ctv-topbar-title {
width: 150px;
flex: 0 0 auto;
min-width: 0;
}
.ctv-topbar-title p {
margin: 0 0 var(--space-1, 2px);
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
font-weight: var(--weight-semibold, 600);
letter-spacing: var(--tracking-caps, 0.06em);
line-height: 1;
text-transform: uppercase;
}
.ctv-topbar-title h1 {
margin: 0;
overflow: hidden;
color: var(--text-primary);
font-size: var(--text-lg, 16px);
font-weight: var(--weight-semibold, 600);
letter-spacing: var(--tracking-tight, -0.01em);
line-height: 1.1;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-topbar-search {
width: min(300px, 28vw);
height: var(--control-h-sm, 28px);
display: flex;
align-items: center;
gap: var(--space-4, 8px);
border: 1px solid var(--border-control);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-bg-sunken);
color: var(--text-disabled);
padding: 0 var(--space-5, 10px);
}
.ctv-topbar-search:focus-within {
border-color: var(--action-primary);
box-shadow: var(--ring-focus);
}
.ctv-topbar-search input {
min-width: 0;
flex: 1;
border: 0;
outline: 0;
background: transparent;
color: var(--text-primary);
font: inherit;
}
.ctv-topbar-search input::placeholder {
color: var(--text-disabled);
}
.ctv-topbar-spacer {
flex: 1;
}
.ctv-topbar-tools {
display: flex;
align-items: center;
gap: var(--space-3, 6px);
}
.ctv-topbar-divider {
width: 1px;
height: 18px;
margin: 0 var(--space-2, 4px);
background: var(--border-hairline);
}
.ctv-avatar {
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid color-mix(in srgb, var(--action-primary) 35%, transparent);
border-radius: var(--radius-pill, 999px);
background: var(--ctv-accent-soft);
color: var(--action-primary);
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--text-2xs, 11px);
font-weight: var(--weight-semibold, 600);
}
.ctv-main {
min-width: 0;
overflow: auto;
padding: var(--space-7, 16px) var(--pad-page, 32px) var(--pad-page, 32px);
}
.ctv-theme-switcher {
display: flex;
align-items: center;
justify-content: flex-end;
gap: var(--gap-inline, 8px);
margin-bottom: var(--space-7, 16px);
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
}
.ctv-theme-switcher > span {
font-weight: var(--weight-semibold, 600);
letter-spacing: var(--tracking-caps, 0.06em);
text-transform: uppercase;
}
.ctv-theme-switcher div {
display: flex;
gap: var(--space-3, 6px);
}
.ctv-theme-swatch {
width: 24px;
height: 24px;
border: 2px solid transparent;
border-radius: var(--radius-pill, 999px);
cursor: pointer;
padding: 0;
}
.ctv-theme-swatch:focus-visible {
outline: 0;
box-shadow: var(--ring-focus);
}
.ctv-theme-swatch[aria-pressed="true"] {
border-color: var(--text-primary);
}
.ctv-theme-swatch-warm {
background: #e08a3c;
}
.ctv-theme-swatch-cool {
background: #5b7cfa;
}
.ctv-theme-swatch-dual {
background: linear-gradient(135deg, #5b7cfa 0 50%, #f0663f 50% 100%);
}
.ctv-connect {
position: relative;
}
.ctv-connect-button {
height: var(--control-h-sm, 28px);
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
border: 1px solid var(--border-control);
border-radius: var(--radius-sm, 5px);
background: var(--surface-raised);
color: var(--text-primary);
cursor: pointer;
padding: 0 var(--space-5, 10px);
font: inherit;
font-weight: var(--weight-medium, 500);
}
.ctv-connect-button svg:first-child {
color: var(--action-primary);
}
.ctv-connect-dismiss {
position: fixed;
inset: 0;
z-index: 40;
border: 0;
background: transparent;
cursor: default;
}
.ctv-connect-menu {
position: absolute;
top: calc(100% + 8px);
right: 0;
z-index: 41;
width: 340px;
display: grid;
gap: var(--space-4, 8px);
border: 1px solid var(--border-control);
border-radius: var(--radius-md, 7px);
background: var(--surface-raised);
box-shadow: var(--shadow-pop);
padding: 14px;
}
.ctv-connect-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-2, 4px);
}
.ctv-connect-header strong {
font-size: var(--text-sm, 13px);
}
.ctv-connect-header span,
.ctv-connect-menu p {
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
}
.ctv-connect-endpoint {
min-width: 0;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-5, 10px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-bg-sunken);
padding: var(--space-5, 10px);
}
.ctv-connect-endpoint-icon {
width: 30px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-xs, 3px);
background: var(--surface-selected);
color: var(--action-primary);
}
.ctv-connect-endpoint div {
min-width: 0;
display: grid;
gap: var(--space-1, 2px);
}
.ctv-connect-endpoint strong {
color: var(--text-primary);
font-size: var(--text-xs, 12px);
}
.ctv-connect-endpoint code {
overflow: hidden;
color: var(--text-secondary);
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--text-2xs, 11px);
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-connect-endpoint button {
display: inline-flex;
align-items: center;
gap: var(--space-2, 4px);
border: 1px solid var(--border-control);
border-radius: var(--radius-xs, 3px);
background: var(--surface-raised);
color: var(--text-secondary);
cursor: pointer;
padding: var(--space-2, 4px) var(--space-4, 8px);
font: inherit;
font-size: var(--text-2xs, 11px);
font-weight: var(--weight-medium, 500);
}
.ctv-connect-option {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-bg-sunken);
color: var(--text-primary);
cursor: pointer;
padding: var(--space-4, 8px) var(--space-5, 10px);
font-size: var(--text-xs, 12px);
}
.ctv-connect-option input {
accent-color: var(--action-primary);
}
.ctv-connect-menu p {
display: flex;
align-items: center;
gap: var(--space-3, 6px);
margin: var(--space-2, 4px) 0 0;
}
.ctv-screen-stack {
display: grid;
gap: var(--space-7, 16px);
}
.ctv-dashboard-grid {
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(260px, 1fr);
gap: var(--space-7, 16px);
align-items: start;
}
.ctv-card-title h2,
.ctv-placeholder-layout h3 {
margin: 0;
color: var(--text-primary);
font-size: var(--text-md, 14px);
font-weight: var(--weight-semibold, 600);
line-height: 1.2;
}
.ctv-onair-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-5, 10px);
}
.ctv-onair-card {
min-width: 0;
display: grid;
gap: var(--space-5, 10px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--ctv-bg-sunken);
padding: var(--space-6, 12px);
}
.ctv-onair-head {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-4, 8px);
}
.ctv-onair-head div {
min-width: 0;
display: grid;
gap: var(--space-1, 2px);
}
.ctv-onair-head code,
.ctv-live-channel-row code {
color: var(--text-secondary);
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--text-2xs, 11px);
}
.ctv-onair-head strong,
.ctv-onair-card p,
.ctv-live-channel-row strong,
.ctv-live-channel-row span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-onair-card p {
margin: 0;
color: var(--text-primary);
}
.ctv-onair-meta {
display: flex;
justify-content: space-between;
gap: var(--space-4, 8px);
color: var(--text-disabled);
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--text-2xs, 11px);
}
.ctv-dashboard-state {
min-height: 220px;
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-4, 8px);
color: var(--text-secondary);
}
.ctv-dashboard-error {
display: grid;
gap: var(--space-3, 6px);
border: 1px solid var(--status-error);
border-radius: var(--radius-md, 7px);
background: var(--ctv-error-soft);
padding: var(--space-6, 12px);
}
.ctv-dashboard-error span {
color: var(--status-error);
font-size: var(--text-xs, 12px);
font-weight: var(--weight-semibold, 600);
letter-spacing: var(--tracking-caps, 0.06em);
text-transform: uppercase;
}
.ctv-dashboard-empty {
min-height: 130px;
display: flex;
grid-column: 1 / -1;
align-items: center;
justify-content: center;
border: 1px dashed var(--border-control);
border-radius: var(--radius-md, 7px);
background: var(--ctv-bg-sunken);
color: var(--text-secondary);
}
.ctv-stat-row {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: var(--space-7, 16px);
}
.ctv-health-panel {
display: grid;
}
.ctv-health-row {
min-height: 46px;
display: grid;
grid-template-columns: auto auto minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-4, 8px);
border-top: 1px solid var(--border-hairline);
padding: 0 var(--space-6, 12px);
}
.ctv-health-row:first-child {
border-top: 0;
}
.ctv-health-detail {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4, 8px);
min-width: 0;
}
/* the detail text lives in this inner span now that the cell is a flex wrapper; the
.ctv-health-row > span:nth-child(3) rule can no longer truncate it (flex box), so restore
the ellipsis affordance here on the actual text element. */
.ctv-health-detail > span:first-child {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-health-action {
flex: none;
white-space: nowrap;
}
.ctv-health-icon {
width: 24px;
height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-xs, 3px);
background: var(--surface-selected);
color: var(--text-secondary);
}
.ctv-health-icon-ok {
color: var(--status-ok);
}
.ctv-health-icon-warn {
color: var(--status-warn);
}
.ctv-health-icon-error {
color: var(--status-error);
}
.ctv-health-icon-idle {
color: var(--text-secondary);
}
.ctv-health-icon-live {
color: var(--status-live);
}
.ctv-health-row strong {
color: var(--text-primary);
font-size: var(--text-xs, 12px);
}
.ctv-health-row > span:nth-child(3) {
overflow: hidden;
color: var(--text-secondary);
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-health-summary {
display: flex;
justify-content: flex-end;
border-top: 1px solid var(--border-hairline);
padding: var(--space-4, 8px) var(--space-6, 12px);
}
.ctv-live-channel-body {
min-height: 96px;
}
.ctv-live-channel-state {
min-height: 96px;
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-4, 8px);
color: var(--text-secondary);
}
.ctv-live-channel-state-error {
min-height: 0;
align-items: flex-start;
flex-direction: column;
justify-content: flex-start;
border: 1px solid var(--status-error);
border-radius: var(--radius-md, 7px);
background: var(--ctv-error-soft);
color: var(--text-primary);
padding: var(--space-6, 12px);
}
.ctv-live-channel-state-error span {
color: var(--status-error);
font-size: var(--text-xs, 12px);
font-weight: var(--weight-semibold, 600);
letter-spacing: var(--tracking-caps, 0.06em);
text-transform: uppercase;
}
.ctv-live-channel-list {
display: grid;
gap: var(--space-3, 6px);
}
.ctv-live-channel-row {
min-height: 48px;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-5, 10px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-bg-sunken);
padding: var(--space-4, 8px);
}
.ctv-live-channel-row div {
min-width: 0;
display: grid;
gap: var(--space-1, 2px);
}
.ctv-live-channel-row span {
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
}
.ctv-live-channel-row code {
border-radius: var(--radius-sm, 5px);
background: var(--surface-selected);
color: var(--text-primary);
padding: var(--space-2, 4px) var(--space-3, 6px);
}
.ctv-placeholder-layout {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
gap: var(--space-7, 16px);
min-height: 160px;
}
.ctv-placeholder-icon {
width: 52px;
height: 52px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid color-mix(in srgb, var(--action-primary) 30%, transparent);
border-radius: var(--radius-md, 7px);
background: var(--ctv-accent-soft);
color: var(--action-primary);
}
.ctv-placeholder-icon svg {
width: 22px;
height: 22px;
}
.ctv-placeholder-layout p {
max-width: 640px;
margin: var(--space-3, 6px) 0 0;
color: var(--text-secondary);
}
.ctv-slot-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-5, 10px);
}
.ctv-slot-grid span {
min-height: 92px;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed var(--border-control);
border-radius: var(--radius-md, 7px);
background: var(--ctv-bg-sunken);
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
font-weight: var(--weight-semibold, 600);
letter-spacing: var(--tracking-caps, 0.06em);
text-transform: uppercase;
}
.ctv-channels-screen {
display: grid;
gap: var(--space-5, 10px);
}
.ctv-channels-actionbar {
min-height: 58px;
display: flex;
align-items: center;
gap: var(--space-5, 10px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--surface-card);
padding: var(--space-5, 10px) var(--space-7, 16px);
}
.ctv-segmented {
display: inline-flex;
gap: 2px;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-bg-sunken);
padding: 2px;
}
.ctv-segmented button {
height: 26px;
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
border: 0;
border-radius: var(--radius-xs, 3px);
background: transparent;
color: var(--text-secondary);
cursor: pointer;
padding: 0 11px;
font: inherit;
font-size: var(--text-xs, 12px);
}
.ctv-segmented button[aria-pressed="true"] {
background: var(--ctv-surface-3);
color: var(--text-primary);
font-weight: var(--weight-medium, 500);
}
.ctv-segmented code,
.ctv-channels-selected code,
.ctv-channels-live code,
.ctv-channels-footer code,
.ctv-channel-group-row code,
.ctv-channel-number code {
font-family: var(--font-mono, ui-monospace, monospace);
font-variant-numeric: tabular-nums;
}
.ctv-channels-selected,
.ctv-channels-live {
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
}
.ctv-channels-selected {
color: var(--text-primary);
font-size: var(--text-sm, 13px);
font-weight: var(--weight-medium, 500);
}
.ctv-channels-spacer {
flex: 1;
}
.ctv-channels-error {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
border: 1px solid color-mix(in srgb, var(--status-error) 32%, transparent);
border-radius: var(--radius-md, 7px);
background: var(--ctv-error-soft);
color: var(--status-error);
padding: var(--space-5, 10px) var(--space-6, 12px);
font-size: var(--text-sm, 13px);
font-weight: var(--weight-medium, 500);
}
.ctv-channels-table-frame {
overflow: hidden;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--surface-card);
}
.ctv-channels-table-scroll {
overflow: auto;
}
.ctv-channels-table {
width: 100%;
min-width: 920px;
border-collapse: collapse;
}
.ctv-channels-table th {
position: sticky;
top: 0;
z-index: 2;
height: 34px;
border-bottom: 1px solid var(--border-hairline);
background: var(--surface-card);
color: var(--text-disabled);
padding: 0 var(--pad-cell-x, 16px);
text-align: left;
white-space: nowrap;
font-size: var(--text-2xs, 11px);
font-weight: var(--weight-medium, 500);
letter-spacing: var(--tracking-caps, 0.06em);
text-transform: uppercase;
}
.ctv-channels-table td {
height: 56px;
border-top: 1px solid var(--border-hairline);
color: var(--text-primary);
padding: 0 var(--pad-cell-x, 16px);
vertical-align: middle;
}
.ctv-logs-sort-button {
display: inline-flex;
align-items: center;
gap: 4px;
background: none;
border: none;
margin: 0;
padding: 0;
color: inherit;
font: inherit;
letter-spacing: inherit;
text-transform: inherit;
cursor: pointer;
}
.ctv-logs-sort-button:hover,
.ctv-logs-sort-button:focus-visible {
color: var(--text-primary);
}
.ctv-channel-check {
width: 38px;
padding-left: 14px !important;
}
.ctv-channel-group-row td {
height: 32px;
border-top: 1px solid var(--border-hairline);
border-bottom: 1px solid var(--border-hairline);
background: var(--ctv-bg-sunken);
}
.ctv-channel-group-row span {
display: inline-flex;
align-items: center;
gap: var(--space-4, 8px);
color: var(--text-secondary);
font-size: var(--text-2xs, 11px);
font-weight: var(--weight-semibold, 600);
letter-spacing: var(--tracking-caps, 0.06em);
text-transform: uppercase;
}
.ctv-channel-group-row svg {
color: var(--text-disabled);
}
.ctv-channel-row-live {
background: var(--ctv-live-soft);
box-shadow: inset 2px 0 0 var(--status-live);
}
.ctv-channel-row-selected {
background: var(--ctv-accent-soft);
}
.ctv-channel-row-dim td:not(:last-child) {
opacity: 0.58;
}
.ctv-channel-number {
width: 74px;
}
.ctv-channel-number span {
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
color: var(--status-live);
}
.ctv-channel-identity {
min-width: 220px;
display: flex;
align-items: center;
gap: 11px;
}
.ctv-channel-identity > div {
min-width: 0;
display: grid;
grid-template-columns: auto auto;
align-items: center;
gap: 3px var(--space-4, 8px);
}
.ctv-channel-identity strong {
overflow: hidden;
color: var(--text-primary);
font-weight: var(--weight-medium, 500);
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-channel-identity small {
grid-column: 1 / -1;
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
line-height: 1;
}
.ctv-channel-markers {
display: inline-flex;
gap: var(--space-2, 4px);
}
.ctv-channel-marker {
min-width: 15px;
height: 15px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border-control);
border-radius: var(--radius-xs, 3px);
background: var(--ctv-surface-3);
color: var(--text-disabled);
padding: 0 3px;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 9px;
font-weight: var(--weight-semibold, 600);
line-height: 1;
}
.ctv-channel-now {
width: 280px;
}
.ctv-channel-now > div {
max-width: 250px;
display: grid;
gap: var(--space-3, 6px);
}
.ctv-channel-now span {
overflow: hidden;
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-channel-now > span {
color: var(--text-faint);
}
.ctv-channel-ffmpeg {
width: 140px;
color: var(--text-secondary) !important;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--text-xs, 12px);
}
.ctv-channel-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 2px;
}
/* ChannelPreviewPanel (#60) — the video needs a max-width so it never overflows the slide-over
body, and the forced-HLS caveat needs a little breathing room around the shared warn-callout. */
.ctv-preview-video {
display: block;
width: 100%;
max-width: 100%;
border-radius: var(--radius-sm);
}
.ctv-preview-caveat {
margin: 0 0 var(--space-6, 12px);
}
/* Two classes (higher specificity than .ctv-detail-infogrid alone) so this margin actually wins
over that shared rule's `margin: 0`, without weakening .ctv-detail-infogrid for its other
consumer (MediaDetailScreen), which relies on margin:0 there. */
.ctv-preview-facts.ctv-detail-infogrid {
margin-top: var(--space-6, 12px);
}
.ctv-channels-footer {
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid var(--border-hairline);
color: var(--text-secondary);
padding: var(--space-5, 10px) var(--space-7, 16px);
font-size: var(--text-xs, 12px);
}
.ctv-schedule-screen {
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
gap: var(--space-6, 12px);
}
.ctv-schedule-header {
display: flex;
align-items: center;
gap: var(--space-6, 12px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--surface-card);
padding: var(--space-6, 12px) var(--space-8, 20px);
}
.ctv-schedule-header-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: var(--radius-sm, 5px);
background: var(--ctv-accent-soft);
color: var(--ctv-accent);
}
.ctv-schedule-header > div {
flex: 1;
min-width: 0;
}
.ctv-schedule-header h2,
.ctv-schedule-inspector-head strong {
margin: 0;
color: var(--text-primary);
font-size: var(--text-md, 14px);
font-weight: var(--weight-semibold, 600);
line-height: 1.2;
}
.ctv-schedule-header p,
.ctv-schedule-inspector-head span {
margin: var(--space-2, 4px) 0 0;
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
line-height: 1.2;
}
.ctv-schedule-header code,
.ctv-schedule-block-duration,
.ctv-schedule-rail code,
.ctv-schedule-fill-chip code {
font-family: var(--font-mono, ui-monospace, monospace);
font-variant-numeric: tabular-nums;
}
.ctv-schedule-grid {
display: grid;
grid-template-columns: minmax(420px, 1fr) minmax(360px, 460px);
gap: var(--space-8, 20px);
min-height: 0;
flex: 1;
}
.ctv-schedule-lineup-panel,
.ctv-schedule-inspector {
min-height: 0;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--surface-card);
}
.ctv-schedule-lineup-panel {
display: flex;
flex-direction: column;
}
.ctv-schedule-panel-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-5, 10px);
border-bottom: 1px solid var(--border-hairline);
padding: var(--space-5, 10px) var(--space-7, 16px);
}
.ctv-schedule-panel-head > span {
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
font-weight: var(--weight-semibold, 600);
line-height: 1;
text-transform: uppercase;
}
.ctv-schedule-lineup {
flex: 1;
min-height: 0;
overflow: auto;
padding: var(--space-6, 12px);
}
.ctv-schedule-lineup-row {
display: grid;
grid-template-columns: 58px minmax(0, 1fr);
gap: var(--space-5, 10px);
}
.ctv-schedule-rail {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2, 4px);
padding-top: var(--space-7, 16px);
color: var(--text-disabled);
font-size: 9px;
}
.ctv-schedule-rail-dot,
.ctv-schedule-rail-dot-fixed {
width: 9px;
height: 9px;
border: 2px solid var(--border-control);
border-radius: var(--radius-pill, 999px);
}
.ctv-schedule-rail-dot-fixed {
border-color: var(--ctv-accent);
background: var(--ctv-accent);
}
.ctv-schedule-block {
position: relative;
display: grid;
gap: var(--space-4, 8px);
margin-bottom: var(--space-6, 12px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--ctv-surface-1);
padding: var(--space-5, 10px);
transition:
background var(--dur-fast) var(--ease-standard),
border-color var(--dur-fast) var(--ease-standard),
opacity var(--dur-fast) var(--ease-standard);
}
.ctv-schedule-block-active {
border-color: var(--action-primary);
background: var(--ctv-accent-soft);
}
.ctv-schedule-block-dragging {
opacity: 0.45;
}
.ctv-schedule-block-over {
box-shadow: inset 0 2px 0 var(--action-primary);
}
.ctv-schedule-block-main {
display: grid;
grid-template-columns: 16px 38px minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-5, 10px);
width: 100%;
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
padding: 0;
text-align: left;
}
.ctv-schedule-block-main > svg {
color: var(--text-disabled);
cursor: grab;
}
.ctv-schedule-block-title {
display: grid;
min-width: 0;
gap: var(--space-2, 4px);
}
.ctv-schedule-block-title strong {
overflow: hidden;
color: var(--text-primary);
font-size: var(--text-md, 14px);
font-weight: var(--weight-semibold, 600);
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-schedule-block-title small,
.ctv-schedule-block-meta {
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
line-height: 1;
}
.ctv-schedule-fill-chip {
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-bg-sunken);
color: var(--text-primary);
padding: var(--space-3, 6px) var(--space-4, 8px);
white-space: nowrap;
}
.ctv-schedule-fill-chip svg,
.ctv-schedule-block-meta svg,
.ctv-schedule-note svg {
color: var(--text-secondary);
}
.ctv-schedule-block-meta {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
flex-wrap: wrap;
padding-left: 64px;
}
.ctv-schedule-block-meta > span {
display: inline-flex;
align-items: center;
gap: var(--space-2, 4px);
}
.ctv-schedule-block-duration {
margin-left: auto;
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
}
.ctv-schedule-block-actions {
position: absolute;
right: var(--space-3, 6px);
bottom: var(--space-3, 6px);
display: flex;
align-items: center;
gap: 2px;
}
.ctv-schedule-up-icon {
transform: rotate(180deg);
}
.ctv-schedule-empty {
display: flex;
align-items: center;
justify-content: center;
min-height: 180px;
color: var(--text-secondary);
font-size: var(--text-sm, 13px);
}
.ctv-channels-empty {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 16px 0;
}
.ctv-channels-empty-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
}
.ctv-schedule-inspector {
display: flex;
flex-direction: column;
overflow: hidden;
}
.ctv-schedule-inspector-head {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
border-bottom: 1px solid var(--border-hairline);
padding: var(--space-7, 16px);
}
.ctv-schedule-inspector-head > div {
display: grid;
flex: 1;
min-width: 0;
gap: var(--space-2, 4px);
}
.ctv-schedule-inspector-badges {
display: flex;
gap: var(--space-3, 6px);
flex-wrap: wrap;
padding: var(--space-5, 10px) var(--space-7, 16px);
}
.ctv-schedule-inspector .ctv-tabs {
padding: 0 var(--space-7, 16px);
}
.ctv-schedule-inspector-body {
display: flex;
flex: 1;
min-height: 0;
flex-direction: column;
gap: var(--space-6, 12px);
overflow: auto;
padding: var(--space-7, 16px);
}
.ctv-schedule-form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-6, 12px);
}
/* --- Schedules editor (#207) --------------------------------------------- */
.ctv-schedule-header-actions {
display: flex;
gap: var(--space-4, 8px);
align-items: center;
}
.ctv-schedule-toolbar {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
padding: var(--space-4, 8px) var(--space-6, 12px);
border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.08));
border-radius: var(--radius-4, 10px);
background: var(--surface-2, rgba(255, 255, 255, 0.02));
}
.ctv-schedule-toolbar-spacer {
flex: 1;
}
.ctv-schedule-block-index {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 22px;
height: 22px;
padding: 0 6px;
border-radius: 999px;
font-size: 12px;
font-variant-numeric: tabular-nums;
background: var(--surface-3, rgba(255, 255, 255, 0.06));
color: var(--text-muted, rgba(255, 255, 255, 0.6));
}
.ctv-schedule-fieldset {
border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.08));
border-radius: var(--radius-4, 10px);
padding: var(--space-6, 12px);
display: flex;
flex-direction: column;
gap: var(--space-6, 12px);
min-width: 0;
}
.ctv-schedule-fieldset > legend {
padding: 0 var(--space-4, 8px);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: uppercase;
color: var(--text-muted, rgba(255, 255, 255, 0.6));
}
.ctv-schedule-form-stack {
display: flex;
flex-direction: column;
gap: var(--space-6, 12px);
}
.ctv-field-help {
margin: 0;
font-size: 12px;
color: var(--text-muted, rgba(255, 255, 255, 0.6));
}
/* Search + multi-select pickers */
.ctv-picker {
position: relative;
display: flex;
flex-direction: column;
gap: 4px;
}
.ctv-picker-selected {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
padding: 6px 10px;
border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.12));
border-radius: var(--radius-3, 8px);
background: var(--surface-2, rgba(255, 255, 255, 0.02));
}
.ctv-picker-selected > span {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-picker-clear,
.ctv-picker-change {
background: none;
border: none;
cursor: pointer;
color: var(--text-muted, rgba(255, 255, 255, 0.6));
font-size: 12px;
padding: 2px 6px;
border-radius: 6px;
}
.ctv-picker-change:hover,
.ctv-picker-clear:hover {
color: var(--text-strong, #fff);
background: var(--surface-3, rgba(255, 255, 255, 0.06));
}
.ctv-picker-results {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
z-index: 20;
margin: 0;
padding: 4px;
list-style: none;
max-height: 240px;
overflow: auto;
border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.12));
border-radius: var(--radius-3, 8px);
background: var(--surface-1, #1a1a1f);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
}
.ctv-picker-result {
display: block;
width: 100%;
text-align: left;
padding: 7px 9px;
border: none;
background: none;
color: inherit;
cursor: pointer;
border-radius: 6px;
font-size: 13px;
}
/* Results for a superseded query: still visible (hiding them flickers on every keystroke) but
plainly not actionable until their replacement lands. `choose` refuses them regardless — this is
the affordance, not the guard. */
.ctv-picker-results-stale {
opacity: 0.5;
}
.ctv-picker-results-stale .ctv-picker-result {
cursor: default;
}
/* `-active` is the keyboard virtual cursor (aria-activedescendant); it deliberately shares the
hover treatment so pointer and keyboard users get the same affordance. */
.ctv-picker-results:not(.ctv-picker-results-stale) .ctv-picker-result:hover,
.ctv-picker-result-active,
.ctv-picker-result[aria-selected='true'] {
background: var(--surface-3, rgba(255, 255, 255, 0.08));
}
.ctv-picker-error {
color: var(--status-error, #f87171);
border-color: currentColor;
}
.ctv-picker-empty {
padding: 8px 10px;
font-size: 12px;
color: var(--text-muted, rgba(255, 255, 255, 0.6));
border: 1px dashed var(--border-subtle, rgba(255, 255, 255, 0.12));
border-radius: var(--radius-3, 8px);
}
.ctv-multiselect {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.ctv-multiselect-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 10px;
border-radius: 999px;
border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.14));
background: var(--surface-2, rgba(255, 255, 255, 0.02));
color: var(--text-muted, rgba(255, 255, 255, 0.7));
cursor: pointer;
font-size: 12px;
}
.ctv-multiselect-chip-active {
border-color: var(--accent, #6ea8fe);
color: var(--text-strong, #fff);
background: color-mix(in srgb, var(--accent, #6ea8fe) 18%, transparent);
}
.ctv-multiselect-chip:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.ctv-schedule-note {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-bg-sunken);
color: var(--text-secondary);
padding: var(--space-6, 12px);
font-size: var(--text-xs, 12px);
line-height: 1.4;
}
.ctv-playouts-screen {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
gap: var(--space-6, 12px);
}
.ctv-playouts-header {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--surface-card);
padding: var(--space-6, 12px) var(--space-8, 20px);
}
.ctv-playouts-header-spacer {
flex: 1;
}
.ctv-playouts-grid {
display: grid;
grid-template-columns: 260px minmax(0, 1fr);
gap: var(--space-8, 20px);
flex: 1;
min-height: 0;
}
.ctv-playouts-rail,
.ctv-playouts-monitor {
min-height: 0;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--surface-card);
}
.ctv-playouts-rail {
display: flex;
flex-direction: column;
overflow: hidden;
}
.ctv-playouts-filter {
display: grid;
gap: var(--space-4, 8px);
border-bottom: 1px solid var(--border-hairline);
padding: var(--space-6, 12px);
}
.ctv-playouts-filter > span {
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
}
.ctv-playouts-list {
display: grid;
align-content: start;
gap: var(--space-2, 4px);
overflow: auto;
padding: var(--space-4, 8px);
}
.ctv-playout-option {
display: grid;
grid-template-columns: 30px minmax(0, 1fr) 10px;
align-items: center;
gap: var(--space-5, 10px);
width: 100%;
border: 0;
border-radius: var(--radius-sm, 5px);
background: transparent;
color: inherit;
cursor: pointer;
padding: var(--space-4, 8px);
text-align: left;
}
.ctv-playout-option:hover,
.ctv-playout-option-active {
background: var(--ctv-accent-soft);
}
.ctv-playout-option > span {
display: grid;
min-width: 0;
gap: var(--space-2, 4px);
}
.ctv-playout-option code,
.ctv-playouts-title code,
.ctv-playout-now-copy code,
.ctv-playout-upcoming code,
.ctv-playout-upcoming small,
.ctv-playout-timeline code {
font-family: var(--font-mono, ui-monospace, monospace);
font-variant-numeric: tabular-nums;
}
.ctv-playout-option code {
color: var(--status-live);
font-size: var(--text-2xs, 11px);
}
.ctv-playout-option strong,
.ctv-playout-next strong {
overflow: hidden;
color: var(--text-primary);
font-size: var(--text-sm, 13px);
font-weight: var(--weight-semibold, 600);
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-playout-option small,
.ctv-playout-next small {
overflow: hidden;
color: var(--text-secondary);
font-size: var(--text-2xs, 11px);
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-playouts-monitor {
display: flex;
flex-direction: column;
gap: var(--space-7, 16px);
overflow: auto;
padding: var(--space-8, 20px);
}
.ctv-playouts-title {
display: flex;
align-items: center;
gap: var(--space-6, 12px);
}
.ctv-playouts-title > div {
display: grid;
min-width: 0;
gap: var(--space-2, 4px);
}
.ctv-playouts-title span {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
}
.ctv-playouts-title h2,
.ctv-playout-now-copy h3 {
margin: 0;
color: var(--text-primary);
font-weight: var(--weight-semibold, 600);
line-height: 1.2;
}
.ctv-playouts-title h2 {
font-size: var(--text-md, 14px);
}
.ctv-playout-now {
display: grid;
grid-template-columns: 210px minmax(0, 1fr);
overflow: hidden;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--surface-card);
}
.ctv-playout-preview {
display: flex;
position: relative;
align-items: center;
justify-content: center;
min-height: 150px;
border-right: 1px solid var(--border-hairline);
background: var(--ctv-bg-sunken);
color: var(--ctv-accent);
}
.ctv-playout-preview .ctv-badge {
position: absolute;
left: var(--space-5, 10px);
bottom: var(--space-5, 10px);
}
.ctv-playout-now-copy {
display: flex;
justify-content: center;
flex-direction: column;
gap: var(--space-5, 10px);
min-width: 0;
padding: var(--space-8, 20px);
}
.ctv-playout-now-copy > span {
color: var(--status-live);
font-size: var(--text-2xs, 11px);
font-weight: var(--weight-semibold, 600);
text-transform: uppercase;
}
.ctv-playout-now-copy h3 {
font-size: var(--text-lg, 16px);
}
.ctv-playout-now-copy > div:last-child {
display: flex;
justify-content: space-between;
gap: var(--space-6, 12px);
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
}
.ctv-playouts-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-7, 16px);
}
.ctv-playout-next {
display: flex;
align-items: center;
gap: var(--space-6, 12px);
}
.ctv-playout-next > svg {
color: var(--ctv-accent);
flex: 0 0 auto;
}
.ctv-playout-next > span {
display: grid;
min-width: 0;
gap: var(--space-2, 4px);
}
.ctv-playout-detail-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-5, 10px);
}
.ctv-playout-timeline {
display: grid;
gap: var(--space-4, 8px);
}
.ctv-playout-timeline > div {
display: flex;
position: relative;
overflow: hidden;
height: 34px;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-bg-sunken);
}
.ctv-playout-timeline span {
display: flex;
align-items: center;
overflow: hidden;
border-right: 1px solid var(--ctv-bg);
background: var(--ctv-accent-soft);
color: var(--ctv-accent);
font-size: var(--text-2xs, 11px);
font-weight: var(--weight-medium, 500);
padding: 0 var(--space-4, 8px);
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-playout-timeline .ctv-playout-timeline-filler {
background: var(--ctv-surface-3);
color: var(--text-secondary);
}
.ctv-playout-timeline i {
position: absolute;
top: -3px;
bottom: -3px;
width: 2px;
background: var(--status-live);
box-shadow: 0 0 8px var(--status-live);
}
.ctv-playout-timeline p {
display: flex;
justify-content: space-between;
margin: 0;
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
}
.ctv-playout-timeline p span {
display: inline-flex;
align-items: center;
gap: var(--space-2, 4px);
color: var(--status-live);
}
.ctv-playout-upcoming > div {
display: grid;
grid-template-columns: 58px 14px minmax(0, 1fr) auto 64px;
align-items: center;
gap: var(--space-5, 10px);
border-top: 1px solid var(--border-hairline);
padding: var(--space-5, 10px) var(--space-7, 16px);
}
.ctv-playout-upcoming > div:first-child {
border-top: 0;
}
.ctv-playout-upcoming-now {
background: var(--ctv-live-soft);
}
.ctv-playout-upcoming svg {
color: var(--ctv-accent);
}
.ctv-playout-upcoming span {
overflow: hidden;
color: var(--text-primary);
font-size: var(--text-sm, 13px);
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-playout-upcoming code,
.ctv-playout-upcoming small {
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
}
.ctv-libraries-screen {
display: flex;
flex-direction: column;
min-height: 0;
gap: var(--space-6, 12px);
}
.ctv-libraries-header {
display: flex;
align-items: center;
gap: var(--space-7, 16px);
justify-content: space-between;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--surface-card);
padding: var(--space-7, 16px) var(--space-8, 20px);
}
.ctv-libraries-header h2 {
margin: var(--space-2, 4px) 0;
color: var(--text-primary);
font-size: var(--text-xl, 20px);
line-height: 1.15;
}
.ctv-libraries-header p {
margin: 0;
color: var(--text-secondary);
font-size: var(--text-sm, 13px);
}
.ctv-libraries-header-actions,
.ctv-libraries-summary {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
flex-wrap: wrap;
}
.ctv-libraries-summary {
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
}
.ctv-libraries-summary span {
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
}
.ctv-libraries-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
align-items: start;
gap: var(--space-7, 16px);
}
.ctv-library-source-card,
.ctv-library-add-card {
overflow: hidden;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-md, 7px);
background: var(--surface-card);
}
.ctv-library-source-head {
display: grid;
grid-template-columns: 34px minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-5, 10px);
border-bottom: 1px solid var(--border-hairline);
padding: var(--space-6, 12px) var(--space-7, 16px);
}
.ctv-library-kind-icon,
.ctv-library-media-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
}
.ctv-library-source-actions {
display: flex;
align-items: center;
gap: var(--space-2, 4px);
}
.ctv-library-kind-icon {
width: 34px;
height: 34px;
border-radius: var(--radius-sm, 5px);
background: var(--ctv-surface-3);
color: var(--ctv-accent);
}
.ctv-library-kind-plex {
color: #e5a00d;
}
.ctv-library-kind-jellyfin {
color: #8a5cf6;
}
.ctv-library-kind-emby {
color: #52b54b;
}
.ctv-library-source-title {
display: flex;
align-items: center;
min-width: 0;
gap: var(--space-4, 8px);
}
.ctv-library-source-title h3,
.ctv-library-add-card h3 {
margin: 0;
color: var(--text-primary);
font-size: var(--text-sm, 13px);
line-height: 1.15;
}
.ctv-library-source-head span:not(.ctv-library-kind-icon),
.ctv-library-source-foot,
.ctv-library-row-main span,
.ctv-library-row-main small,
.ctv-library-add-card p {
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
}
.ctv-library-list {
display: grid;
}
.ctv-library-row {
display: grid;
grid-template-columns: 28px minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-5, 10px);
border-top: 1px solid var(--border-hairline);
padding: var(--space-5, 10px) var(--space-7, 16px);
}
.ctv-library-row:first-child {
border-top: 0;
}
.ctv-library-media-icon {
width: 28px;
height: 28px;
border-radius: var(--radius-xs, 3px);
background: var(--ctv-bg-sunken);
color: var(--text-secondary);
}
.ctv-library-row-main {
display: grid;
min-width: 0;
gap: var(--space-2, 4px);
}
.ctv-library-row-main strong {
overflow: hidden;
color: var(--text-primary);
font-size: var(--text-sm, 13px);
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-library-row-status {
display: flex;
align-items: center;
justify-content: flex-end;
gap: var(--space-4, 8px);
}
.ctv-library-progress {
display: grid;
width: 118px;
gap: var(--space-3, 6px);
}
.ctv-library-source-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-5, 10px);
border-top: 1px solid var(--border-hairline);
background: var(--ctv-bg-sunken);
padding: var(--space-5, 10px) var(--space-7, 16px);
}
.ctv-library-source-foot span {
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
}
.ctv-library-empty {
color: var(--text-disabled);
font-size: var(--text-xs, 12px);
padding: var(--space-7, 16px);
}
.ctv-library-add-card {
display: flex;
min-height: 190px;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: var(--space-6, 12px);
border-style: dashed;
border-color: var(--border-control);
padding: var(--space-7, 16px);
}
.ctv-library-add-card p {
margin: var(--space-2, 4px) 0 0;
line-height: 1.4;
}
.ctv-library-add-options {
display: flex;
flex-wrap: wrap;
gap: var(--space-3, 6px);
}
.ctv-library-add-options span {
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-surface-2);
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
font-weight: var(--weight-medium, 500);
padding: var(--space-3, 6px) var(--space-5, 10px);
}
.ctv-epg-screen {
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
gap: var(--space-6, 12px);
}
.ctv-epg-toolbar {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-5, 10px);
padding: var(--space-5, 10px) 0;
border-bottom: 1px solid var(--border-hairline);
}
.ctv-epg-filter {
width: 190px;
flex: 0 1 190px;
}
.ctv-epg-window {
min-width: 0;
max-width: 420px;
display: flex;
flex: 1 1 240px;
align-items: center;
gap: var(--space-4, 8px);
}
.ctv-epg-window span {
color: var(--text-disabled);
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--text-2xs, 11px);
font-variant-numeric: tabular-nums;
}
.ctv-epg-window input {
min-width: 0;
flex: 1;
accent-color: var(--action-primary);
}
.ctv-epg-grid-shell {
min-height: 420px;
overflow: hidden;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--surface-app);
}
.ctv-epg-scroll {
width: 100%;
height: 100%;
overflow: auto;
}
.ctv-epg-grid {
position: relative;
width: max-content;
min-height: 100%;
}
.ctv-epg-time-head {
position: sticky;
top: 0;
z-index: 4;
display: flex;
border-bottom: 1px solid var(--border-hairline);
background: var(--surface-app);
}
.ctv-epg-rail-head,
.ctv-epg-channel-rail {
position: sticky;
left: 0;
z-index: 3;
flex: 0 0 auto;
border-right: 1px solid var(--border-hairline);
background: var(--surface-card);
}
.ctv-epg-rail-head {
z-index: 5;
background: var(--surface-app);
}
.ctv-epg-time-slot {
flex: 0 0 auto;
display: flex;
align-items: center;
border-right: 1px solid var(--border-hairline);
color: var(--text-secondary);
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--text-xs, 12px);
font-variant-numeric: tabular-nums;
padding: 0 var(--space-5, 10px);
}
.ctv-epg-now-marker {
position: absolute;
z-index: 2;
width: 2px;
background: var(--status-live);
pointer-events: none;
}
.ctv-epg-now-marker span {
position: absolute;
top: -5px;
left: -4px;
width: 10px;
height: 10px;
border-radius: var(--radius-pill, 999px);
background: var(--status-live);
box-shadow: 0 0 8px var(--status-live);
}
.ctv-epg-row {
display: flex;
border-bottom: 1px solid var(--border-hairline);
}
.ctv-epg-channel-rail {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
padding: 0 var(--space-6, 12px);
}
.ctv-epg-channel-rail code {
width: 30px;
flex: 0 0 auto;
color: var(--status-live);
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--text-xs, 12px);
font-weight: var(--weight-medium, 500);
font-variant-numeric: tabular-nums;
}
.ctv-epg-channel-rail > span:not(.ctv-channel-logo):not(.ctv-status-dot) {
min-width: 0;
overflow: hidden;
color: var(--text-primary);
font-size: var(--text-sm, 13px);
font-weight: var(--weight-medium, 500);
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-epg-track {
position: relative;
flex: 1;
min-width: 0;
}
.ctv-epg-track-alt {
background: color-mix(in srgb, var(--surface-raised) 18%, transparent);
}
.ctv-epg-empty {
position: absolute;
top: 50%;
left: var(--space-6, 12px);
transform: translateY(-50%);
color: var(--text-disabled);
font-size: var(--text-xs, 12px);
}
.ctv-epg-programme {
position: absolute;
top: 5px;
bottom: 5px;
display: flex;
min-width: 0;
flex-direction: column;
gap: 3px;
overflow: hidden;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--surface-raised);
padding: var(--space-4, 8px) var(--space-5, 10px);
}
.ctv-epg-programme-live {
border-color: var(--status-live);
background: var(--ctv-live-soft);
}
.ctv-epg-programme-filler {
background: var(--ctv-bg-sunken);
}
.ctv-epg-programme-title {
display: flex;
min-width: 0;
align-items: center;
gap: var(--space-3, 6px);
}
.ctv-epg-programme-title strong,
.ctv-epg-programme span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-epg-programme-title strong {
color: var(--text-primary);
font-size: var(--text-xs, 12px);
font-weight: var(--weight-medium, 500);
}
.ctv-epg-programme-filler .ctv-epg-programme-title strong {
color: var(--text-disabled);
}
.ctv-epg-programme span {
color: var(--text-secondary);
font-size: var(--text-2xs, 11px);
}
.ctv-epg-programme small {
margin-top: auto;
overflow: hidden;
color: var(--text-disabled);
font-size: 9px;
font-weight: var(--weight-medium, 500);
letter-spacing: var(--tracking-caps, 0.06em);
text-overflow: ellipsis;
text-transform: uppercase;
white-space: nowrap;
}
.ctv-epg-programme-live small {
color: var(--status-live);
}
/* ---- Channel Builder (#89) --------------------------------------------- */
/* The builder is a full-height flagship screen. `.ctv-main` is a bounded,
scrolling grid track (100vh - topbar); the ThemeSwitcher + main padding sit
above us, so we bound our own height off the viewport to get three
independently scrolling columns rather than one long page scroll. */
.ctv-builder {
display: flex;
flex-direction: column;
height: calc(100vh - 132px);
min-height: 460px;
margin: calc(var(--space-7, 16px) * -1) calc(var(--pad-page, 32px) * -1) calc(var(--pad-page, 32px) * -1);
border-top: 1px solid var(--border-hairline);
}
.ctv-hide-sb {
scrollbar-width: thin;
scrollbar-color: var(--border-control) transparent;
}
.ctv-hide-sb::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.ctv-hide-sb::-webkit-scrollbar-thumb {
background: var(--border-control);
border-radius: 8px;
}
.ctv-hide-sb::-webkit-scrollbar-track {
background: transparent;
}
.ctv-builder-toolbar {
display: flex;
align-items: center;
gap: var(--space-6, 12px);
padding: var(--space-6, 12px) 20px;
border-bottom: 1px solid var(--border-hairline);
flex: 0 0 auto;
}
.ctv-builder-toolbar-tile {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: var(--radius-sm);
background: var(--ctv-accent-soft);
color: var(--ctv-accent);
flex: 0 0 auto;
}
.ctv-builder-toolbar-titles {
flex: 1;
min-width: 0;
}
.ctv-builder-title {
font: var(--weight-semibold) var(--text-md) / 1 var(--font-sans);
color: var(--text-primary);
}
.ctv-builder-summary {
margin-top: 3px;
font: var(--text-xs) / 1 var(--font-sans);
color: var(--text-secondary);
}
.ctv-mono {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
}
.ctv-builder-error {
display: flex;
align-items: flex-start;
gap: var(--space-4, 8px);
padding: var(--space-4, 8px) 20px;
border-bottom: 1px solid var(--border-hairline);
background: var(--ctv-error-soft);
color: var(--status-error);
font: var(--text-xs) / 1.4 var(--font-sans);
flex: 0 0 auto;
}
.ctv-builder-body {
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) 304px;
}
.ctv-builder-col {
display: flex;
flex-direction: column;
min-height: 0;
min-width: 0;
overflow: hidden;
border-right: 1px solid var(--border-hairline);
}
.ctv-builder-col-rail {
border-right: none;
background: var(--surface-card);
}
.ctv-builder-col-head {
padding: 14px 16px 10px;
display: flex;
flex-direction: column;
gap: var(--space-5, 10px);
flex: 0 0 auto;
}
.ctv-builder-col-head-row {
flex-direction: row;
align-items: center;
}
.ctv-builder-scroll {
flex: 1;
min-height: 0;
min-width: 0;
overflow-y: auto;
overflow-x: hidden;
}
.ctv-builder-scroll-body {
padding: 4px 16px 16px;
}
.ctv-builder-rail-scroll {
padding: 14px 16px 24px;
display: flex;
flex-direction: column;
gap: var(--space-7, 16px);
}
.ctv-builder-headrow {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
flex-wrap: wrap;
row-gap: var(--space-4, 8px);
}
.ctv-builder-eyebrow {
font: var(--weight-semibold) var(--text-2xs) / 1 var(--font-sans);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-disabled);
}
.ctv-builder-chiprow {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.ctv-builder-chip {
padding: 4px 10px;
border-radius: var(--radius-pill);
cursor: pointer;
font: var(--weight-medium) var(--text-2xs) / 1.4 var(--font-sans);
background: var(--ctv-surface-2);
color: var(--text-secondary);
border: 1px solid var(--border-hairline);
}
.ctv-builder-chip-on {
background: var(--ctv-accent-soft);
color: var(--ctv-accent);
border-color: var(--ctv-accent-ring, rgba(224, 138, 60, 0.35));
}
.ctv-builder-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
gap: var(--space-5, 10px);
}
.ctv-builder-compact-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.ctv-builder-empty {
padding: 40px 0;
text-align: center;
font: var(--text-xs) / 1.5 var(--font-sans);
color: var(--text-disabled);
}
.ctv-builder-loadmore {
margin-top: var(--space-6, 12px);
display: flex;
justify-content: center;
}
/* poster placeholder */
.ctv-builder-poster {
position: relative;
flex: 0 0 auto;
overflow: hidden;
border-radius: var(--radius-sm);
border: 1px solid var(--border-hairline);
}
.ctv-builder-poster img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.ctv-builder-poster-icon {
position: absolute;
display: inline-flex;
z-index: 1;
}
.ctv-builder-poster-scrim {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 18px 8px 8px;
background: linear-gradient(to top, rgba(11, 13, 17, 0.92), transparent);
z-index: 1;
}
.ctv-builder-poster-title {
font: var(--weight-semibold) var(--text-xs) / 1.15 var(--font-sans);
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-builder-poster-meta {
margin-top: 3px;
font: var(--text-2xs) / 1 var(--font-mono);
color: var(--text-secondary);
}
/* hover lift comes from the shared .ctv-lift utility */
.ctv-builder-libcard {
position: relative;
cursor: grab;
}
.ctv-builder-libcard-poster {
position: relative;
border-radius: var(--radius-sm);
}
.ctv-builder-libcard-compact {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
padding: 6px 8px;
border-radius: var(--radius-sm);
cursor: grab;
background: var(--surface-card);
border: 1px solid var(--border-hairline);
}
.ctv-builder-compact-title {
font: var(--weight-medium) var(--text-xs) / 1.15 var(--font-sans);
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-builder-compact-meta {
margin-top: 2px;
font: var(--text-2xs) / 1 var(--font-sans);
color: var(--text-secondary);
}
.ctv-builder-added-check {
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--action-primary);
color: var(--text-on-accent);
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--shadow-sm);
animation: ctv-pop 260ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* Seasons drill-in affordance on show tiles (issue #180) */
.ctv-builder-seasons-btn {
position: absolute;
left: 6px;
bottom: 6px;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
border-radius: var(--radius-sm);
border: 1px solid var(--border-hairline);
background: color-mix(in srgb, var(--surface-card) 82%, transparent);
color: var(--text-primary);
font: var(--weight-medium) var(--text-2xs) / 1 var(--font-sans);
cursor: pointer;
backdrop-filter: blur(2px);
}
.ctv-builder-seasons-btn:hover {
border-color: color-mix(in srgb, var(--action-primary) 45%, transparent);
color: var(--action-primary);
}
.ctv-builder-seasons-list {
display: flex;
flex-direction: column;
gap: 6px;
max-height: 420px;
overflow-y: auto;
}
.ctv-builder-seasons-row {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
padding: 6px 8px;
border-radius: var(--radius-sm);
background: var(--surface-card);
border: 1px solid var(--border-hairline);
}
.ctv-builder-seasons-row-title {
flex: 1;
min-width: 0;
font: var(--weight-medium) var(--text-xs) / 1.15 var(--font-sans);
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@keyframes ctv-pop {
from {
transform: scale(0.2);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
/* media browse / search / trash poster grid */
.ctv-media-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: var(--space-6, 12px);
}
/* Dim the stale result set while a refetch is in flight (search/media browse — issue #221). The
per-card mutation menu is withheld in the same state; navigation stays live so no pointer-events
change here. */
.ctv-media-grid-dim {
opacity: 0.5;
transition: opacity var(--dur-fast, 120ms) var(--ease-standard, ease);
}
.ctv-media-card {
position: relative;
border-radius: var(--radius-sm);
border: 1px solid var(--border-hairline);
background: var(--surface-card);
overflow: hidden;
transition:
border-color 120ms ease,
box-shadow 120ms ease;
}
.ctv-media-card[role='button'] {
cursor: pointer;
}
.ctv-media-card-selected {
border-color: var(--action-primary);
box-shadow: 0 0 0 1px var(--action-primary);
}
/* Deep-link target highlight (#220), e.g. `#episode-{id}` from search/browse episode cards.
The ring itself (border + 2px box-shadow) persists for as long as the hash names this card;
only the outer glow pulse fades out shortly after mount. */
.ctv-media-card-highlighted {
border-color: var(--action-primary);
box-shadow: 0 0 0 2px var(--action-primary);
animation: ctv-media-card-highlight-pulse 2400ms ease-out 1;
}
@keyframes ctv-media-card-highlight-pulse {
0% {
box-shadow: 0 0 0 4px var(--action-primary);
}
100% {
box-shadow: 0 0 0 2px var(--action-primary);
}
}
.ctv-media-card-poster {
position: relative;
overflow: hidden;
}
.ctv-media-card-poster img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.ctv-media-card-icon {
position: absolute;
top: 7px;
left: 7px;
display: inline-flex;
z-index: 1;
}
.ctv-media-card-check {
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--action-primary);
color: var(--text-on-accent);
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
box-shadow: var(--shadow-sm);
z-index: 2;
}
.ctv-media-card-actions {
position: absolute;
top: 6px;
right: 6px;
z-index: 3;
display: inline-flex;
opacity: 0;
transition: opacity 120ms ease;
}
.ctv-media-card:hover .ctv-media-card-actions,
.ctv-media-card:focus-within .ctv-media-card-actions {
opacity: 1;
}
/* Always keep the actions reachable by keyboard even when not hovered. */
.ctv-media-card-actions:focus-within {
opacity: 1;
}
/* Add-to menu popover */
.ctv-addto-menu {
position: relative;
display: inline-flex;
}
.ctv-addto-menu-popover {
position: absolute;
top: calc(100% + 4px);
right: 0;
z-index: 40;
min-width: 160px;
display: flex;
flex-direction: column;
padding: 4px;
border-radius: var(--radius-sm);
border: 1px solid var(--border-hairline);
background: var(--ctv-surface-3, var(--surface-card));
box-shadow: var(--shadow-md, 0 8px 24px rgb(0 0 0 / 0.35));
}
.ctv-addto-menu-item {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 7px 10px;
border: none;
border-radius: var(--radius-xs, 6px);
background: transparent;
color: var(--text-primary);
font: inherit;
font-size: var(--text-xs, 12px);
font-weight: var(--weight-medium, 500);
line-height: 1.2;
text-align: left;
cursor: pointer;
}
.ctv-addto-menu-item:hover,
.ctv-addto-menu-item:focus-visible {
background: var(--ctv-surface-2, var(--surface-hover));
}
.ctv-media-card-body {
padding: 8px 10px 10px;
}
.ctv-media-card-title {
font: var(--weight-semibold) var(--text-xs) / 1.2 var(--font-sans);
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-media-card-meta {
margin-top: 3px;
font: var(--text-2xs) / 1.2 var(--font-sans);
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Media detail pages (#141/#161) */
.ctv-detail {
position: relative;
display: flex;
flex-direction: column;
gap: var(--space-6, 12px);
}
.ctv-detail-fanart {
position: absolute;
inset: -8px -8px auto -8px;
height: 260px;
background-size: cover;
background-position: center 20%;
opacity: 0.12;
filter: saturate(0.9);
border-radius: var(--radius-md, 8px);
pointer-events: none;
z-index: 0;
-webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / 100%), transparent);
mask-image: linear-gradient(to bottom, rgb(0 0 0 / 100%), transparent);
}
.ctv-detail-head {
position: relative;
z-index: 1;
display: flex;
gap: var(--space-7, 16px);
flex-wrap: wrap;
}
.ctv-detail-poster {
width: 200px;
max-width: 40vw;
aspect-ratio: 2 / 3;
object-fit: cover;
border-radius: var(--radius-sm);
border: 1px solid var(--border-hairline);
background: var(--surface-card);
flex-shrink: 0;
}
.ctv-detail-poster-empty {
background: repeating-linear-gradient(135deg, var(--surface-card) 0 8px, var(--surface-app) 8px 16px);
}
.ctv-detail-headinfo {
display: flex;
flex-direction: column;
gap: var(--space-4, 8px);
min-width: 260px;
flex: 1;
}
.ctv-detail-title {
margin: 0;
font: var(--weight-semibold) var(--text-xl, 20px) / 1.2 var(--font-sans);
color: var(--text-primary);
}
.ctv-detail-subtitle {
color: var(--text-secondary);
font-size: var(--text-sm, 13px);
}
.ctv-detail-plot {
margin: 0;
color: var(--text-primary);
line-height: 1.5;
max-width: 70ch;
}
.ctv-detail-actions {
display: flex;
gap: var(--space-5, 10px);
flex-wrap: wrap;
margin-top: var(--space-4, 8px);
}
.ctv-detail-path {
font-family: var(--font-mono, monospace);
font-size: var(--text-xs, 12px);
color: var(--text-secondary);
word-break: break-all;
}
.ctv-detail-chipstack {
display: flex;
flex-direction: column;
gap: var(--space-5, 10px);
}
.ctv-detail-chiprow {
display: flex;
gap: var(--space-5, 10px);
align-items: baseline;
flex-wrap: wrap;
}
.ctv-detail-chiplabel {
min-width: 130px;
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.ctv-detail-chips {
display: flex;
gap: var(--space-3, 6px);
flex-wrap: wrap;
flex: 1;
}
.ctv-detail-section {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
gap: var(--space-5, 10px);
}
.ctv-detail-heading {
margin: 0;
font: var(--weight-semibold) var(--text-md, 15px) / 1.2 var(--font-sans);
color: var(--text-primary);
}
.ctv-detail-actors {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
gap: var(--space-6, 12px);
}
.ctv-detail-actor {
display: flex;
flex-direction: column;
gap: 4px;
text-align: center;
}
.ctv-detail-actor-thumb {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
border-radius: 50%;
border: 1px solid var(--border-hairline);
}
.ctv-detail-actor-thumb-empty {
background: var(--surface-card);
}
.ctv-detail-actor-name {
font-size: var(--text-xs, 12px);
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-detail-actor-role {
font-size: var(--text-2xs, 11px);
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-detail-info {
display: flex;
flex-direction: column;
gap: var(--space-7, 16px);
}
.ctv-detail-infogrid {
display: grid;
grid-template-columns: max-content 1fr;
gap: var(--space-3, 6px) var(--space-6, 12px);
margin: 0;
}
.ctv-detail-infogrid dt {
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.ctv-detail-infogrid dd {
margin: 0;
color: var(--text-primary);
font-size: var(--text-sm, 13px);
}
.ctv-detail-info-block {
display: flex;
flex-direction: column;
gap: var(--space-4, 8px);
}
.ctv-imgfolder-row {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
padding: var(--space-3, 6px) 0;
border-bottom: 1px solid var(--border-hairline);
}
.ctv-imgfolder-name {
font-size: var(--text-sm, 13px);
color: var(--text-primary);
font-weight: var(--weight-medium, 500);
}
.ctv-imgfolder-meta {
font-size: var(--text-2xs, 11px);
color: var(--text-secondary);
}
.ctv-imgfolder-duration {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: var(--text-2xs, 11px);
color: var(--text-disabled);
}
.ctv-media-section-header {
display: flex;
align-items: baseline;
gap: var(--space-4, 8px);
margin: var(--space-7, 16px) 0 var(--space-5, 10px);
}
.ctv-media-section-header h2 {
font: var(--weight-semibold) var(--text-md) / 1.2 var(--font-sans);
color: var(--text-primary);
margin: 0;
}
.ctv-media-section-count {
font: var(--text-xs) / 1 var(--font-sans);
color: var(--text-secondary);
}
.ctv-media-section-footer {
display: flex;
justify-content: center;
margin: var(--space-5, 10px) 0 var(--space-7, 16px);
}
/* lineup */
.ctv-builder-lineup-list {
display: flex;
flex-direction: column;
gap: 7px;
}
.ctv-builder-lineup-row {
display: flex;
align-items: center;
gap: 11px;
padding: 9px 10px;
border-radius: var(--radius-md);
background: var(--surface-card);
border: 1px solid var(--border-hairline);
transition: opacity var(--dur-fast);
}
.ctv-builder-lineup-row-error {
border-color: var(--status-error);
}
.ctv-builder-grip {
display: inline-flex;
cursor: grab;
color: var(--ctv-text-faint);
flex: 0 0 auto;
}
.ctv-builder-order {
width: 22px;
flex: 0 0 auto;
text-align: center;
font: var(--weight-medium) var(--text-xs) / 1 var(--font-mono);
color: var(--text-secondary);
}
.ctv-builder-row-main {
flex: 1;
min-width: 0;
}
.ctv-builder-row-title {
font: var(--weight-semibold) var(--text-sm) / 1.2 var(--font-sans);
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-builder-row-sub {
margin-top: 2px;
font: var(--text-2xs) / 1 var(--font-sans);
color: var(--text-secondary);
}
.ctv-builder-row-dur {
font: var(--text-2xs) / 1 var(--font-mono);
color: var(--text-disabled);
flex: 0 0 auto;
}
.ctv-builder-lineup-empty {
min-height: 220px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-6, 12px);
text-align: center;
border: 1px dashed var(--border-control);
border-radius: var(--radius-md);
color: var(--text-disabled);
padding: 24px;
}
.ctv-builder-lineup-empty-over {
border-color: var(--ctv-accent);
background: var(--ctv-accent-soft);
}
.ctv-builder-lineup-hint {
margin-top: 4px;
padding: 10px 12px;
border-radius: var(--radius-md);
border: 1px dashed var(--border-hairline);
display: flex;
align-items: center;
gap: var(--space-4, 8px);
color: var(--text-disabled);
font: var(--text-2xs) / 1 var(--font-sans);
}
/* rail */
.ctv-builder-rail-block {
display: flex;
flex-direction: column;
gap: var(--space-5, 10px);
}
.ctv-builder-auto-chip {
font: 9px / 1 var(--font-mono);
padding: 2px 5px;
border-radius: var(--radius-xs);
background: var(--ctv-accent-soft);
color: var(--ctv-accent);
}
.ctv-builder-dropzone {
display: flex;
gap: var(--space-6, 12px);
padding: var(--space-6, 12px);
border-radius: var(--radius-sm);
cursor: pointer;
background: var(--ctv-bg-sunken);
border: 1px dashed var(--border-control);
}
.ctv-builder-dropzone-over {
border-color: var(--ctv-accent);
}
.ctv-builder-preview-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-5, 10px);
}
.ctv-builder-preview-tile {
padding: 10px;
border-radius: var(--radius-sm);
background: var(--surface-card);
border: 1px solid var(--border-hairline);
}
.ctv-builder-toggle-row {
display: flex;
align-items: flex-start;
gap: 11px;
padding: 10px 0;
cursor: pointer;
}
.ctv-builder-toggle-live {
gap: var(--space-5, 10px);
padding: 10px 11px;
border-radius: var(--radius-sm);
border: 1px solid transparent;
transition: background 240ms cubic-bezier(0.16, 1, 0.3, 1), border-color 240ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ctv-builder-toggle-live-on {
background: var(--ctv-live-soft);
border-color: var(--ctv-live);
}
.ctv-builder-toggle-label {
font: var(--weight-medium) var(--text-sm) / 1.2 var(--font-sans);
color: var(--text-primary);
}
.ctv-builder-toggle-help {
margin-top: 2px;
font: var(--text-2xs) / 1.35 var(--font-sans);
color: var(--text-secondary);
}
.ctv-builder-overrides-tag {
font: var(--text-2xs) / 1 var(--font-sans);
color: var(--text-disabled);
padding: 2px 6px;
border-radius: var(--radius-xs);
background: var(--ctv-surface-2);
border: 1px solid var(--border-hairline);
}
.ctv-builder-live-pill {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 7px;
border-radius: var(--radius-pill);
background: var(--ctv-live-soft);
color: var(--ctv-live);
font: var(--weight-semibold) 9px / 1.4 var(--font-sans);
letter-spacing: 0.06em;
}
.ctv-builder-hairline {
height: 1px;
background: var(--border-hairline);
}
.ctv-builder-tpl-button {
width: 100%;
display: flex;
align-items: center;
gap: var(--space-5, 10px);
padding: 10px 11px;
border-radius: var(--radius-sm);
cursor: pointer;
text-align: left;
background: var(--ctv-bg-sunken);
border: 1px solid var(--border-control);
}
.ctv-builder-tpl-tile {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: var(--radius-xs);
background: var(--ctv-surface-3);
color: var(--ctv-accent);
flex: 0 0 auto;
}
.ctv-builder-tpl-name {
font: var(--weight-medium) var(--text-sm) / 1 var(--font-sans);
color: var(--text-primary);
}
.ctv-builder-tpl-desc {
margin-top: 3px;
font: var(--text-2xs) / 1.3 var(--font-sans);
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctv-builder-tpl-list {
margin-top: 6px;
display: flex;
flex-direction: column;
gap: 4px;
padding: 4px;
border-radius: var(--radius-sm);
background: var(--ctv-bg-sunken);
border: 1px solid var(--border-hairline);
}
.ctv-builder-tpl-option {
display: flex;
align-items: center;
gap: 9px;
padding: 8px 9px;
border-radius: var(--radius-xs);
cursor: pointer;
text-align: left;
background: transparent;
border: none;
}
.ctv-builder-tpl-option-on {
background: color-mix(in srgb, var(--action-primary) 14%, transparent);
}
.ctv-builder-tpl-option-name {
font: var(--weight-medium) var(--text-xs) / 1 var(--font-sans);
color: var(--text-primary);
}
.ctv-builder-tpl-save {
display: flex;
align-items: center;
gap: 7px;
padding: 8px 9px;
border-radius: var(--radius-xs);
cursor: pointer;
background: none;
border: none;
color: var(--text-secondary);
font: var(--weight-medium) var(--text-xs) / 1 var(--font-sans);
}
.ctv-builder-tpl-empty {
padding: 10px 11px;
border-radius: var(--radius-sm);
background: var(--ctv-bg-sunken);
border: 1px solid var(--border-hairline);
font: var(--text-2xs) / 1.4 var(--font-sans);
color: var(--text-secondary);
}
.ctv-builder-config-chips {
margin-top: var(--space-4, 8px);
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.ctv-builder-config-chip {
padding: 3px 8px;
border-radius: var(--radius-xs);
background: var(--ctv-surface-2);
border: 1px solid var(--border-hairline);
font: var(--text-2xs) / 1.4 var(--font-sans);
color: var(--text-secondary);
}
.ctv-builder-adv {
border-top: 1px solid var(--border-hairline);
padding-top: 14px;
}
.ctv-builder-adv-head {
display: flex;
align-items: center;
gap: 9px;
margin-bottom: var(--space-5, 10px);
}
.ctv-builder-adv-head-title {
font: var(--weight-medium) var(--text-sm) / 1 var(--font-sans);
color: var(--text-primary);
}
.ctv-builder-adv-count {
font: var(--text-2xs) / 1 var(--font-mono);
color: var(--text-disabled);
}
.ctv-builder-adv-tabs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-4, 8px);
}
.ctv-builder-adv-tab {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 8px 10px;
border-radius: var(--radius-sm);
cursor: pointer;
font: var(--weight-medium) var(--text-xs) / 1 var(--font-sans);
background: var(--ctv-surface-2);
color: var(--text-secondary);
border: 1px solid var(--border-hairline);
transition: background 200ms cubic-bezier(0.16, 1, 0.3, 1), color 200ms, border-color 200ms;
}
.ctv-builder-adv-tab-on {
background: color-mix(in srgb, var(--action-primary) 14%, transparent);
color: var(--action-primary);
border-color: color-mix(in srgb, var(--action-primary) 38%, transparent);
}
.ctv-builder-adv-panel {
overflow: hidden;
transition: max-height 340ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ctv-builder-adv-fields {
margin-top: var(--space-6, 12px);
display: flex;
flex-direction: column;
gap: 14px;
}
.ctv-builder-adv-info {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
font: var(--text-2xs) / 1.35 var(--font-sans);
color: var(--text-secondary);
}
.ctv-builder-adv-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-4, 8px);
}
.ctv-builder-adv-readonly {
padding: 7px 9px;
border-radius: var(--radius-xs);
background: var(--ctv-bg-sunken);
border: 1px solid var(--border-hairline);
opacity: 0.72;
}
.ctv-builder-adv-readonly-key {
font: var(--text-2xs) / 1 var(--font-sans);
color: var(--text-disabled);
}
.ctv-builder-adv-readonly-value {
margin-top: 3px;
font: var(--weight-medium) var(--text-xs) / 1 var(--font-sans);
color: var(--text-primary);
}
.ctv-builder-adv-switchrow {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4, 8px);
padding: 7px 9px;
border-radius: var(--radius-xs);
background: var(--ctv-bg-sunken);
border: 1px solid var(--border-hairline);
font: var(--text-xs) / 1.2 var(--font-sans);
color: var(--text-primary);
}
/* segmented control (builder-local) */
.ctv-builder-seg {
position: relative;
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
padding: 2px;
border-radius: var(--radius-sm);
background: var(--ctv-bg-sunken);
border: 1px solid var(--border-hairline);
}
.ctv-builder-seg-thumb {
position: absolute;
top: 2px;
bottom: 2px;
left: 2px;
background: var(--ctv-surface-3);
border-radius: var(--radius-xs);
box-shadow: var(--shadow-sm);
transition: transform 300ms cubic-bezier(0.16, 1, 0.3, 1);
pointer-events: none;
}
.ctv-builder-seg-btn {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 5px 10px;
border: none;
background: transparent;
cursor: pointer;
color: var(--text-secondary);
transition: color 220ms cubic-bezier(0.16, 1, 0.3, 1);
font: var(--weight-medium) var(--text-2xs) / 1 var(--font-sans);
}
.ctv-builder-seg-btn-on {
color: var(--text-primary);
}
.ctv-builder-linkbtn {
background: none;
border: none;
cursor: pointer;
font: var(--text-2xs) / 1 var(--font-sans);
color: var(--text-secondary);
padding: 0;
}
.ctv-builder-disclosure {
align-self: flex-start;
display: inline-flex;
align-items: center;
gap: 5px;
background: none;
border: none;
cursor: pointer;
padding: 2px 0;
font: var(--text-2xs) / 1 var(--font-sans);
color: var(--text-secondary);
}
.ctv-builder-sep-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-5, 10px);
padding: 10px;
border-radius: var(--radius-sm);
background: var(--ctv-bg-sunken);
border: 1px solid var(--border-hairline);
}
/* Shared motion utilities — ported from the prototype (chicorytv-admin/app.html).
Subtle, iOS/macOS-style: quick, ease-out, small. */
.ctv-lift {
transition:
transform 240ms cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 240ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ctv-lift:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.ctv-press {
transition:
transform 150ms cubic-bezier(0.16, 1, 0.3, 1),
background 220ms cubic-bezier(0.16, 1, 0.3, 1),
color 220ms cubic-bezier(0.16, 1, 0.3, 1),
border-color 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ctv-press:active {
transform: scale(0.96);
}
@media (prefers-reduced-motion: reduce) {
.ctv-lift,
.ctv-press,
.ctv-builder-seg-thumb,
.ctv-builder-seg-btn,
.ctv-builder-adv-panel,
.ctv-builder-adv-tab,
.ctv-builder-lineup-row,
.ctv-builder-libcard,
.ctv-builder-toggle-live,
.ctv-builder-added-check {
transition: none;
animation: none;
}
.ctv-lift:hover,
.ctv-press:active {
transform: none;
}
}
@media (max-width: 980px) {
.ctv-app-shell {
grid-template-columns: 1fr;
overflow: auto;
}
.ctv-sidebar,
.ctv-shell-body {
height: auto;
}
.ctv-sidebar {
position: sticky;
top: 0;
z-index: 20;
}
.ctv-nav,
.ctv-sidebar-health,
.ctv-sidebar-toggle {
display: none;
}
/* The collapse feature is desktop-only (the nav is hidden here). If the sidebar was collapsed on
desktop, don't let the rail's brand-hiding leave an empty header on mobile — restore the brand
(#396). */
.ctv-app-shell-collapsed .ctv-brand {
padding: 0 14px;
justify-content: flex-start;
}
.ctv-app-shell-collapsed .ctv-brand img,
.ctv-app-shell-collapsed .ctv-brand-wordmark {
display: revert;
}
.ctv-topbar {
flex-wrap: wrap;
height: auto;
min-height: var(--topbar-h, 52px);
padding: var(--space-4, 8px) var(--space-6, 12px);
}
.ctv-topbar-search {
width: min(100%, 340px);
}
.ctv-main {
padding: var(--space-6, 12px);
}
.ctv-dashboard-grid,
.ctv-stat-row,
.ctv-onair-grid,
.ctv-slot-grid {
grid-template-columns: 1fr;
}
.ctv-channels-actionbar {
align-items: flex-start;
flex-wrap: wrap;
}
.ctv-channels-spacer {
display: none;
}
.ctv-segmented {
width: 100%;
}
.ctv-segmented button {
flex: 1;
justify-content: center;
}
.ctv-schedule-header {
align-items: flex-start;
flex-wrap: wrap;
}
.ctv-schedule-grid,
.ctv-schedule-form-grid,
.ctv-playouts-grid,
.ctv-playout-detail-grid,
.ctv-playouts-cards,
.ctv-libraries-grid {
grid-template-columns: 1fr;
}
.ctv-libraries-header,
.ctv-library-source-foot {
align-items: flex-start;
flex-direction: column;
}
.ctv-library-row {
grid-template-columns: 28px minmax(0, 1fr);
}
.ctv-library-row-status {
grid-column: 2 / -1;
justify-content: flex-start;
}
.ctv-epg-toolbar {
align-items: stretch;
flex-direction: column;
}
.ctv-epg-filter,
.ctv-epg-window {
width: 100%;
max-width: none;
}
.ctv-epg-grid-shell {
min-height: 360px;
}
.ctv-schedule-block-main {
grid-template-columns: 16px 38px minmax(0, 1fr);
}
.ctv-schedule-fill-chip {
grid-column: 2 / -1;
justify-self: start;
}
.ctv-builder {
height: auto;
margin: calc(var(--space-6, 12px) * -1);
}
.ctv-builder-body {
grid-template-columns: 1fr;
}
.ctv-builder-col,
.ctv-builder-col-rail {
overflow: visible;
border-right: none;
border-bottom: 1px solid var(--border-hairline);
}
.ctv-builder-scroll {
overflow: visible;
}
.ctv-settings {
height: auto;
flex-direction: column;
margin: calc(var(--space-7, 16px) * -1) calc(var(--pad-page, 32px) * -1) calc(var(--pad-page, 32px) * -1);
}
.ctv-settings-rail {
width: auto;
border-right: none;
border-bottom: 1px solid var(--border-hairline);
}
}
/* ---- Settings (#93) ------------------------------------------------------ */
/* Full-height flagship screen, same pattern as .ctv-builder: break out of
.ctv-main's page padding to get an independently-scrolling rail + pane. */
@keyframes ctv-fade-in {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.ctv-fade-in {
animation: ctv-fade-in 240ms var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
}
.ctv-settings {
display: flex;
height: calc(100vh - 132px);
min-height: 460px;
margin: calc(var(--space-7, 16px) * -1) calc(var(--pad-page, 32px) * -1) calc(var(--pad-page, 32px) * -1);
border-top: 1px solid var(--border-hairline);
}
.ctv-settings-rail {
display: flex;
flex-direction: column;
width: 224px;
flex: 0 0 auto;
padding: var(--space-6, 12px) var(--space-4, 8px);
overflow: auto;
border-right: 1px solid var(--border-hairline);
}
.ctv-settings-rail-item {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
width: 100%;
padding: 9px 10px;
margin-bottom: 2px;
border: none;
border-radius: var(--radius-sm);
background: transparent;
cursor: pointer;
text-align: left;
}
.ctv-settings-rail-item:hover {
background: var(--ctv-surface-2);
}
.ctv-settings-rail-item-active,
.ctv-settings-rail-item-active:hover {
background: var(--ctv-accent-soft);
}
.ctv-settings-rail-icon {
display: inline-flex;
flex: 0 0 auto;
}
.ctv-settings-rail-text {
flex: 1;
min-width: 0;
}
.ctv-settings-rail-label {
font: var(--weight-medium) var(--text-sm) / 1 var(--font-sans);
}
.ctv-settings-rail-hint {
margin-top: 3px;
font: var(--text-2xs) / 1 var(--font-sans);
color: var(--text-disabled);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
.ctv-settings-body {
flex: 1;
min-width: 0;
min-height: 0;
position: relative;
display: flex;
flex-direction: column;
}
.ctv-settings-scroll {
flex: 1;
min-height: 0;
overflow: auto;
padding: var(--space-8, 20px) var(--space-9, 24px) 96px;
}
.ctv-settings-loading,
.ctv-settings-error {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
padding: var(--space-9, 24px);
color: var(--text-secondary);
}
.ctv-settings-error {
color: var(--status-error);
}
.ctv-settings-pane {
max-width: 760px;
display: flex;
flex-direction: column;
gap: var(--space-7, 16px);
animation: ctv-fade-in 240ms var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
}
.ctv-settings-pane-title {
font: var(--weight-semibold) var(--text-lg, 15px) / 1.2 var(--font-sans);
color: var(--text-primary);
}
.ctv-settings-pane-subtitle {
margin-top: 5px;
font: var(--text-sm) / 1.45 var(--font-sans);
color: var(--text-secondary);
}
.ctv-settings-row {
display: flex;
align-items: center;
gap: var(--space-8, 20px);
padding: 13px var(--space-7, 16px);
border-top: 1px solid var(--border-hairline);
}
.ctv-settings-row-main {
flex: 1;
min-width: 0;
}
.ctv-settings-row-label {
font: var(--weight-medium) var(--text-sm) / 1.3 var(--font-sans);
color: var(--text-primary);
}
.ctv-settings-row-help {
margin-top: 3px;
font: var(--text-xs) / 1.45 var(--font-sans);
color: var(--text-disabled);
}
.ctv-settings-row-control {
display: flex;
justify-content: flex-end;
}
.ctv-settings-unit {
font: var(--text-2xs) / 1 var(--font-sans);
color: var(--text-disabled);
}
.ctv-settings-callout {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
padding: 10px 14px;
border-radius: var(--radius-sm);
background: var(--ctv-surface-2);
border: 1px dashed var(--border-control);
color: var(--text-disabled);
}
.ctv-settings-callout span {
flex: 1;
font: var(--text-xs) / 1.4 var(--font-sans);
color: var(--text-secondary);
}
.ctv-settings-warn-callout {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
padding: 10px 14px;
border-radius: var(--radius-sm);
background: var(--ctv-warn-soft);
border: 1px solid transparent;
font: var(--text-xs) / 1.4 var(--font-sans);
color: var(--text-secondary);
}
.ctv-troubleshoot-pre {
max-height: 60vh;
overflow: auto;
margin: 0;
padding: 12px;
border-radius: var(--radius-sm);
background: var(--ctv-surface-2);
border: 1px solid var(--border-hairline);
font: var(--text-xs) / 1.5 var(--font-mono);
color: var(--text-secondary);
white-space: pre-wrap;
word-break: break-word;
}
.ctv-textarea {
width: 100%;
padding: 10px 12px;
border-radius: var(--radius-sm);
background: var(--ctv-surface-2);
border: 1px solid var(--border-hairline);
color: var(--text-primary);
font: var(--text-xs) / 1.5 var(--font-mono);
}
.ctv-textarea:focus {
outline: none;
border-color: var(--status-accent, var(--ctv-accent, #4c8bf5));
}
.ctv-validation-messages {
margin: 0;
padding-left: 20px;
display: flex;
flex-direction: column;
gap: var(--space-3, 6px);
font: var(--text-xs) / 1.5 var(--font-mono);
}
.ctv-detail-grid {
display: grid;
grid-template-columns: minmax(140px, max-content) 1fr;
gap: var(--space-4, 8px) var(--space-7, 16px);
margin: 0;
}
.ctv-detail-grid dt {
color: var(--text-disabled);
font-size: var(--text-2xs, 11px);
font-weight: var(--weight-medium, 500);
letter-spacing: var(--tracking-caps, 0.06em);
text-transform: uppercase;
align-self: center;
}
.ctv-detail-grid dd {
margin: 0;
color: var(--text-primary);
}
.ctv-settings-flush-row {
display: flex;
align-items: center;
gap: var(--space-6, 12px);
padding: 10px var(--space-7, 16px);
border-top: 1px solid var(--border-hairline);
}
.ctv-settings-flush-row-main {
flex: 1;
min-width: 0;
font: var(--text-sm) / 1 var(--font-sans);
color: var(--text-primary);
}
.ctv-settings-flush-row-title {
font-weight: var(--weight-medium);
}
.ctv-settings-flush-row-detail {
margin-left: var(--space-4, 8px);
font: var(--text-2xs) / 1 var(--font-mono);
color: var(--text-disabled);
}
.ctv-settings-flush-footer {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
padding: 10px var(--space-7, 16px);
border-top: 1px solid var(--border-hairline);
background: var(--ctv-bg-sunken);
}
.ctv-settings-uuid-row {
display: flex;
align-items: center;
gap: var(--space-3, 6px);
width: 100%;
justify-content: flex-end;
color: var(--text-secondary);
font: var(--text-xs) / 1 var(--font-sans);
overflow: hidden;
}
.ctv-settings-uuid-row span {
overflow: hidden;
text-overflow: ellipsis;
}
.ctv-settings-savebar-wrap {
position: absolute;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
padding: 0 var(--space-9, 24px) 18px;
pointer-events: none;
}
.ctv-settings-savebar {
pointer-events: auto;
display: flex;
align-items: center;
gap: var(--space-6, 12px);
padding: 10px 12px 10px 16px;
border-radius: var(--radius-pill);
background: var(--surface-raised);
border: 1px solid var(--border-control);
box-shadow: var(--shadow-pop);
animation: ctv-fade-in 200ms var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
}
.ctv-settings-savebar-text {
font: var(--weight-medium) var(--text-sm) / 1 var(--font-sans);
color: var(--text-primary);
}
.ctv-settings-savebar-error {
font: var(--text-xs) / 1 var(--font-sans);
color: var(--status-error);
}
.ctv-settings-savebar-divider {
width: 1px;
height: 18px;
background: var(--border-hairline);
}
/* inline degraded-state note for reference data that failed to load */
.ctv-settings-ref-failure {
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
font: var(--text-xs) / 1.3 var(--font-sans);
color: var(--text-secondary);
}
.ctv-settings-ref-failure svg {
color: var(--status-warn);
flex: 0 0 auto;
}
@media (prefers-reduced-motion: reduce) {
.ctv-fade-in,
.ctv-settings-pane,
.ctv-settings-savebar {
animation: none;
}
}
/* ---------- collections screen (#140) ---------- */
.ctv-collections {
display: flex;
flex-direction: column;
gap: var(--space-5, 10px);
}
.ctv-collections-loading,
.ctv-collections-empty {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-4, 8px);
padding: var(--space-8, 20px);
color: var(--text-secondary);
font-size: var(--text-sm, 13px);
}
.ctv-collections-detail-title {
font-size: var(--text-sm, 13px);
font-weight: var(--weight-medium, 500);
color: var(--text-primary);
}
.ctv-collections-linkbtn {
border: 0;
background: transparent;
text-align: left;
cursor: pointer;
padding: 0;
color: var(--text-primary);
font: inherit;
font-weight: var(--weight-medium, 500);
}
.ctv-collections-linkbtn:hover {
color: var(--ctv-accent);
}
.ctv-collections-order-toggle {
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
cursor: pointer;
}
/* per-source weight input + computed share (#404) */
.ctv-collections-weight {
display: inline-flex;
align-items: center;
gap: var(--space-3, 6px);
color: var(--text-secondary);
font-size: var(--text-xs, 12px);
}
.ctv-collections-weight-label {
color: var(--text-secondary);
}
.ctv-collections-weight-pct {
min-width: 34px;
text-align: right;
font: var(--text-2xs) / 1 var(--font-mono);
color: var(--text-disabled);
}
.ctv-collections-weight-footer {
gap: var(--space-6, 12px);
}
.ctv-collections-weight-hint {
flex: 1;
min-width: 0;
font-size: var(--text-xs, 12px);
color: var(--text-secondary);
}
.ctv-collections-query {
margin-top: 3px;
font: var(--text-2xs) / 1.3 var(--font-mono);
color: var(--text-disabled);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 520px;
}
/* smart-collection preview + picker */
.ctv-collections-preview {
margin-top: 12px;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--ctv-bg-sunken);
padding: var(--space-5, 10px);
}
.ctv-collections-preview-head {
margin-bottom: var(--space-4, 8px);
}
.ctv-collections-preview-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 4px;
max-height: 220px;
overflow-y: auto;
}
.ctv-collections-preview-list li {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
font-size: var(--text-xs, 12px);
color: var(--text-secondary);
}
.ctv-collections-preview-title {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text-primary);
}
.ctv-collections-preview-type,
.ctv-collections-preview-empty {
font: var(--text-2xs) / 1 var(--font-mono);
color: var(--text-disabled);
}
.ctv-collections-picker-note {
margin: 10px 0 0;
font: var(--text-2xs) / 1.4 var(--font-sans);
color: var(--text-disabled);
}
/* Media-kind filter chips in the add-items dialog (issue #180) */
.ctv-collections-picker-filters {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.ctv-collections-picker-filter {
padding: 4px 12px;
border-radius: var(--radius-pill, 999px);
border: 1px solid var(--border-hairline);
background: var(--surface-card);
color: var(--text-secondary);
font: var(--weight-medium) var(--text-2xs) / 1 var(--font-sans);
cursor: pointer;
}
.ctv-collections-picker-filter:hover {
color: var(--text-primary);
border-color: color-mix(in srgb, var(--action-primary) 35%, transparent);
}
.ctv-collections-picker-filter-active {
background: color-mix(in srgb, var(--action-primary) 14%, transparent);
border-color: color-mix(in srgb, var(--action-primary) 45%, transparent);
color: var(--action-primary);
}
.ctv-collections-picker-results {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 4px;
max-height: 320px;
overflow-y: auto;
}
.ctv-collections-picker-empty {
padding: var(--space-7, 16px);
text-align: center;
color: var(--text-disabled);
font-size: var(--text-xs, 12px);
}
.ctv-collections-picker-row {
display: flex;
align-items: center;
gap: var(--space-4, 8px);
width: 100%;
border: 1px solid var(--border-hairline);
border-radius: var(--radius-sm, 5px);
background: var(--surface-card);
padding: var(--space-4, 8px) var(--space-5, 10px);
cursor: pointer;
font: inherit;
color: var(--text-primary);
text-align: left;
}
.ctv-collections-picker-row-active {
background: color-mix(in srgb, var(--action-primary) 12%, transparent);
border-color: color-mix(in srgb, var(--action-primary) 35%, transparent);
}
.ctv-collections-picker-row-title {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: var(--text-sm, 13px);
}
.ctv-collections-picker-check {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
color: var(--action-primary);
}
/* Playout alternate-schedule / template editors: multi-select day/month chips */
.ctv-chip-toggle {
padding: 3px 9px;
border-radius: 999px;
border: 1px solid var(--ctv-border);
background: var(--ctv-surface);
color: var(--ctv-text-soft);
font-size: 12px;
line-height: 1.4;
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.ctv-chip-toggle:hover {
border-color: var(--ctv-accent);
}
.ctv-chip-toggle-active {
background: var(--ctv-accent-soft);
border-color: var(--ctv-accent);
color: var(--ctv-text);
}
/* Search multi-select action bar (#208): appears below the actionbar while items are selected. */
.ctv-search-selectbar {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
border: 1px solid var(--ctv-accent);
border-radius: var(--radius-md, 7px);
background: var(--ctv-accent-soft);
padding: var(--space-4, 8px) var(--space-7, 16px);
}
.ctv-search-selectbar-count {
font-weight: 600;
color: var(--ctv-text);
}
/* API-key required banner — shell-level 401 pointer (#197). */
.ctv-auth-banner {
display: flex;
align-items: center;
gap: var(--space-5, 10px);
margin: 12px 24px 0;
padding: 10px 14px;
border-radius: var(--radius-sm);
background: var(--ctv-warn-soft);
border: 1px solid var(--status-warn);
font: var(--text-sm) / 1.4 var(--font-sans);
color: var(--text-primary);
}
.ctv-auth-banner-text {
flex: 1;
}
.ctv-auth-banner-link {
border: 1px solid var(--ctv-accent);
background: transparent;
color: var(--ctv-accent);
border-radius: var(--radius-sm);
padding: 4px 10px;
font: var(--text-xs) / 1 var(--font-sans);
font-weight: 600;
cursor: pointer;
}
.ctv-auth-banner-dismiss {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
padding: 4px;
border-radius: var(--radius-sm);
}
/* Shell-less boot pages (login / setup / checking / error) — the AuthGate renders these OUTSIDE the app
shell, so they own their own full-viewport centered layout (#295). */
.ctv-auth-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 24px;
background: var(--surface-app);
}