/* arr — master-control board. Tokens mirror .impeccable/design.json; components consume tokens, never literals. Committed dark: a control room is lit by its lamps. */ @font-face { font-family: "Rajdhani"; font-style: normal; font-weight: 600; font-display: swap; src: url("/fonts/rajdhani-600-latin.woff2") format("woff2"); } @font-face { font-family: "Rajdhani"; font-style: normal; font-weight: 700; font-display: swap; src: url("/fonts/rajdhani-700-latin.woff2") format("woff2"); } :root { /* ground and panels */ --ground: oklch(0.14 0.01 250); --panel: oklch(0.18 0.012 250); --panel-raised: oklch(0.21 0.014 250); --line: oklch(0.3 0.015 250); --line-strong: oklch(0.42 0.02 250); --highlight: oklch(1 0 0 / 4%); /* ink */ --ink: oklch(0.93 0.008 250); --ink-muted: oklch(0.72 0.015 250); --ink-faint: oklch(0.58 0.015 250); /* signal lamps */ --signal-ok: oklch(0.8 0.17 150); --signal-warn: oklch(0.82 0.14 80); --signal-fault: oklch(0.68 0.21 25); --signal-idle: oklch(0.45 0.02 250); /* the one interactive hue */ --accent: oklch(0.8 0.12 220); --accent-bright: oklch(0.88 0.12 220); /* dead lamp and pressed-control lowlight */ --lamp-off: oklch(0.3 0.012 250); --lowlight: oklch(0 0 0 / 30%); /* release verdicts (§9.3) — rejected is quiet slate, never fault red */ --verdict-eligible: oklch(0.8 0.17 150); --verdict-waived: oklch(0.82 0.14 80); --verdict-rejected: oklch(0.68 0.02 250); /* score heat scale (issue #111) — one list-relative gradient from lowest red to highest green; JS picks the stop, colour stays in tokens */ --score-0: oklch(0.68 0.19 25); --score-1: oklch(0.7 0.185 46); --score-2: oklch(0.72 0.175 67); --score-3: oklch(0.75 0.165 88); --score-4: oklch(0.77 0.16 108); --score-5: oklch(0.79 0.165 129); --score-6: oklch(0.8 0.17 150); /* library state ramp (§4.2): green satisfied, amber gap, violet active, neutral nothing-happening. Satisfaction reuses --signal-ok and a gap --signal-warn, so the only hue this family still owns is the active one, which doubles as the downloading colour. */ --status-airing: oklch(0.78 0.14 305); /* type */ --font-label: system-ui, "Segoe UI", sans-serif; --font-readout: ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono", Menlo, Consolas, monospace; --font-display: "Rajdhani", "Avenir Next Condensed", "Arial Narrow", sans-serif; /* type scale */ --text-2xs: 0.6875rem; --text-xs: 0.75rem; --text-sm: 0.8125rem; --text-base: 1rem; --text-md: 1.0625rem; --text-lg: 1.3125rem; --text-xl: 1.625rem; /* rhythm */ --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem; --space-6: 1.5rem; --space-8: 2rem; --space-12: 3rem; --radius: 4px; --control-h: 2rem; --ease-out: cubic-bezier(0.25, 1, 0.5, 1); } * { box-sizing: border-box; } html, body { margin: 0; padding: 0; } /* a scrollbar appearing must not reflow the rail's wrap */ html { scrollbar-gutter: stable; } body { min-height: 100dvh; display: flex; flex-direction: column; background: var(--ground); color: var(--ink); font-family: var(--font-label); font-size: var(--text-base); line-height: 1.5; } .readout { font-family: var(--font-readout); font-size: var(--text-sm); } .dim { color: var(--ink-muted); } /* ---- rails ---------------------------------------------------------- */ .rail { padding: var(--space-3) var(--space-6); border-bottom: 1px solid var(--line); background: var(--panel); box-shadow: inset 0 1px var(--highlight); } /* contents share the deck's column; the panel itself stays full-bleed */ .rail-inner { display: flex; align-items: center; gap: var(--space-6); width: 100%; max-width: 68rem; margin-inline: auto; } .rail-id { display: flex; align-items: center; gap: var(--space-3); } /* §9.7: the wordmark is a link home — quiet at rest, cyan on approach like every other interactive control */ .wordmark { margin: 0; font-family: var(--font-display); font-size: var(--text-xl); font-weight: 700; letter-spacing: 0.1em; line-height: 1; color: var(--ink); text-decoration: none; } .wordmark:hover, .wordmark:focus-visible { color: var(--accent-bright); } .rail-meta { display: flex; align-items: center; gap: var(--space-4); /* the verdict line used to absorb the slack; without it the readout needs to claim the right edge itself */ margin-left: auto; } /* ---- lamps ---------------------------------------------------------- */ .lamp { --lamp: var(--signal-idle); width: 0.625rem; height: 0.625rem; flex: none; border-radius: 50%; background: var(--lamp); /* the panel's own device: a lit indicator, not a depth shadow */ box-shadow: 0 0 6px var(--lamp), 0 0 14px oklch(from var(--lamp) l c h / 35%); transition: background 300ms var(--ease-out); } .lamp-big { width: 0.875rem; height: 0.875rem; } .lamp[data-state="ok"] { --lamp: var(--signal-ok); } .lamp[data-state="unconfigured"] { --lamp: var(--signal-warn); } .lamp[data-state="degraded"] { --lamp: var(--signal-warn); } .lamp[data-state="unreachable"] { --lamp: var(--signal-fault); } .lamp[data-state="probing"] { --lamp: var(--signal-idle); animation: lamp-probe 1.2s var(--ease-out) infinite alternate; } /* dead air: dark, no glow, no motion */ .lamp[data-state="off"] { --lamp: var(--lamp-off); box-shadow: none; } @keyframes lamp-probe { from { opacity: 0.45; } to { opacity: 1; } } /* power-up: lamps flicker on left to right, once, on first verdict */ @keyframes lamp-strike { 0% { opacity: 0.2; } 40% { opacity: 1; } 55% { opacity: 0.35; } 100% { opacity: 1; } } .chain.powered .lamp { animation: lamp-strike 420ms var(--ease-out) both; animation-delay: calc(var(--strike, 0) * 120ms); } .chain.powered .lamp[data-state="off"] { animation: none; } @media (prefers-reduced-motion: reduce) { .lamp[data-state="probing"], .chain.powered .lamp { animation: none; } } /* ---- signal chain (§9.7: a settings section, no route of its own) ----- */ .chain { width: 100%; display: grid; grid-template-columns: 1fr 2.5rem 1.2fr 2.5rem 1fr; grid-template-areas: "tmdb t1 master out sink" "prow t2 master out sink"; align-items: center; row-gap: var(--space-6); } .area-tmdb { grid-area: tmdb; } .area-prowlarr { grid-area: prow; } .area-t1 { grid-area: t1; } .area-t2 { grid-area: t2; } .area-master { grid-area: master; } .area-out { grid-area: out; } .area-transmission { grid-area: sink; } /* ---- modules --------------------------------------------------------- */ .module { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: inset 0 1px var(--highlight); padding: var(--space-4) var(--space-4) var(--space-3); } .module-master { background: var(--panel-raised); border-color: var(--line-strong); padding: var(--space-6) var(--space-4) var(--space-4); } .module-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1) var(--space-2); } .module-name { margin: 0; font-family: var(--font-display); font-size: var(--text-md); font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; line-height: 1; } .module-master .module-name { font-size: var(--text-lg); } .module-status { margin-left: auto; padding-left: var(--space-3); color: var(--ink-muted); } .module-status[data-tone="ok"] { color: var(--signal-ok); } .module-status[data-tone="warn"] { color: var(--signal-warn); } .module-status[data-tone="fault"] { color: var(--signal-fault); } .module-role { margin: var(--space-2) 0 0; font-size: var(--text-2xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; color: var(--ink-muted); } .module-detail { margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--ink-muted); min-height: 1.2em; } /* ---- bus traces ------------------------------------------------------ */ .bus { display: grid; align-content: center; justify-items: center; } .bus-trace { --trace: var(--line-strong); width: 100%; height: 0.5rem; display: block; transition: filter 300ms var(--ease-out); } .bus-trace line { stroke: var(--trace); stroke-width: 1; } .bus-arrow { fill: var(--trace); } .bus-trace[data-tone="ok"] { --trace: var(--signal-ok); } .bus-trace[data-tone="warn"] { --trace: var(--signal-warn); } .bus-trace[data-tone="fault"] { --trace: var(--signal-fault); } /* ---- controls -------------------------------------------------------- */ .control { display: inline-flex; align-items: center; justify-content: center; min-height: var(--control-h); padding: 0 var(--space-4); font-family: var(--font-readout); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent); background: var(--panel-raised); border: 1px solid var(--line-strong); border-radius: var(--radius); box-shadow: inset 0 1px var(--highlight); cursor: pointer; transition: border-color 150ms var(--ease-out), color 150ms var(--ease-out); } .control:hover { border-color: var(--accent); color: var(--accent-bright); } .control:active { box-shadow: inset 0 1px var(--lowlight); } .control:disabled { color: var(--ink-faint); border-color: var(--line); cursor: default; } /* the engaged view control stays lit while its surface is up */ .control[aria-pressed="true"] { border-color: var(--accent); color: var(--accent-bright); } :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } /* ---- unified search (§9.2) ------------------------------------------- */ .rail-search { position: relative; flex: 1; max-width: 30rem; display: flex; align-items: center; } .search-input { width: 100%; min-height: var(--control-h); padding: 0 var(--space-8) 0 var(--space-3); color: var(--ink); background: var(--ground); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: inset 0 1px var(--lowlight); caret-color: var(--accent); transition: border-color 150ms var(--ease-out); } .search-input::placeholder { color: var(--ink-faint); opacity: 1; } .search-input:hover { border-color: var(--line-strong); } .search-input:focus { border-color: var(--accent); outline: none; } .search-hint { position: absolute; right: var(--space-3); padding: 0 var(--space-1); font-size: var(--text-2xs); color: var(--ink-faint); border: 1px solid var(--line); border-radius: var(--radius); pointer-events: none; } /* ---- search deck ------------------------------------------------------ */ .deck { width: 100%; max-width: 68rem; margin: 0 auto; padding: var(--space-8) var(--space-6) var(--space-12); flex: 1; } .deck-status { margin: 0 0 var(--space-4); color: var(--ink-muted); } .deck-status[data-tone="fault"] { color: var(--signal-fault); } /* a sweep in flight borrows the rail's probing-lamp idiom, inline */ .deck-status .lamp { display: inline-block; margin-right: var(--space-2); vertical-align: baseline; } .deck-group { margin: 0 0 var(--space-8); } .deck-head { display: flex; align-items: baseline; gap: var(--space-3); padding: 0 var(--space-1) var(--space-2); border-bottom: 1px solid var(--line); } /* the engraved micro-label is the heading itself, not a kicker */ .deck-label { margin: 0; font-size: var(--text-2xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; color: var(--ink-muted); } .deck-count { font-size: var(--text-xs); color: var(--ink-faint); } .deck-rows { margin: 0; padding: 0; list-style: none; } .row { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-1) var(--space-3); width: 100%; padding: var(--space-2) var(--space-1); border-bottom: 1px solid oklch(from var(--line) l c h / 45%); text-align: left; } .row-id { display: inline-flex; align-items: baseline; gap: var(--space-2); min-width: 0; } /* titles are names: the display face, per the Readout Rule in design.json */ .row-title { font-family: var(--font-display); font-size: var(--text-md); font-weight: 600; letter-spacing: 0.02em; color: var(--ink); } .row-year { font-size: var(--text-xs); } .row-chips { display: inline-flex; align-items: center; gap: var(--space-1); margin-left: auto; } .chip { display: inline-flex; align-items: center; gap: var(--space-1); min-height: var(--control-h); padding: 0 var(--space-2); background: var(--panel-raised); border: 1px solid var(--line); border-radius: var(--radius); font-family: var(--font-readout); font-size: var(--text-xs); color: var(--ink-muted); } .chip[data-verdict="eligible"] { color: var(--verdict-eligible); border-color: oklch(from var(--verdict-eligible) l c h / 45%); } /* the dashed border is the visible mark of a relaxed rule */ .chip[data-verdict="waived"] { color: var(--verdict-waived); border-style: dashed; border-color: oklch(from var(--verdict-waived) l c h / 55%); } /* rejected is the policy working, never fault red */ .chip[data-verdict="rejected"] { color: var(--verdict-rejected); } /* media state ramp (§4.2): green on disk, violet downloading, amber wanted and still missing. Unwanted-missing and parked are nothing-happening and stay neutral; `parked` exists so a vanished grab never reads as a gap. */ .chip[data-movie-state="available"] { color: var(--signal-ok); border-color: oklch(from var(--signal-ok) l c h / 45%); } .chip[data-movie-state="downloading"] { color: var(--status-airing); border-color: oklch(from var(--status-airing) l c h / 45%); } .chip[data-movie-state="missing"][data-wanted="true"] { color: var(--signal-warn); border-color: oklch(from var(--signal-warn) l c h / 55%); } /* library status ramp (§4.2): violet while activity is live, green once everything wanted is on disk, amber for a grabbable gap; waiting is between seasons with nothing to do, so it stays neutral */ .chip[data-status="airing"] { color: var(--status-airing); border-color: oklch(from var(--status-airing) l c h / 45%); } .chip[data-status="incomplete"] { color: var(--signal-warn); border-color: oklch(from var(--signal-warn) l c h / 55%); } .chip[data-status="complete"], .chip[data-status="ended"] { color: var(--signal-ok); border-color: oklch(from var(--signal-ok) l c h / 45%); } /* season/series counts chip (§4.2): the same ramp read off N/M itself */ .chip[data-counts="complete"] { color: var(--signal-ok); border-color: oklch(from var(--signal-ok) l c h / 45%); } .chip[data-counts="partial"] { color: var(--signal-warn); border-color: oklch(from var(--signal-warn) l c h / 55%); } /* a TMDB row is the affordance: the whole row opens the add flow */ .row-tmdb { font: inherit; background: none; border: 0; border-bottom: 1px solid oklch(from var(--line) l c h / 45%); cursor: pointer; transition: background 150ms var(--ease-out); } .row-tmdb:hover:not(:disabled):not([aria-disabled="true"]), .row-tmdb:focus-visible:not([aria-disabled="true"]) { background: var(--panel); } .row-tmdb:disabled, .row-tmdb[aria-disabled="true"] { cursor: default; } .row-add { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent); } .row-overview { flex-basis: 100%; overflow: hidden; font-size: var(--text-xs); color: var(--ink-faint); white-space: nowrap; text-overflow: ellipsis; } /* an episode hit's title sits beside its SxxEyy chip: a name, not a readout */ .row-sub { font-size: var(--text-sm); color: var(--ink-muted); } /* ---- rich search rows (#148): poster, rating chip, trailer chip -------- */ .row-rich { align-items: flex-start; } .row-main { display: flex; flex: 1; align-items: baseline; flex-wrap: wrap; gap: var(--space-1) var(--space-3); min-width: 0; } /* w92 is the right order of magnitude for a row; the blank is the same box, so the column edge holds while results stream in */ .row-poster, .row-poster-blank { width: 2.75rem; aspect-ratio: 2 / 3; flex-shrink: 0; align-self: center; border: 1px solid var(--line); border-radius: var(--radius); } .row-poster { display: block; background: var(--panel-raised); object-fit: cover; } .chip[role="button"] { cursor: pointer; transition: color 150ms var(--ease-out), border-color 150ms var(--ease-out); } .chip[role="button"]:hover, .chip[role="button"]:focus-visible { color: var(--accent); border-color: oklch(from var(--accent) l c h / 45%); } .row-trailer[data-state="pending"] { color: var(--ink-faint); } /* no video on TMDB is an ordinary outcome: quiet and dashed, not fault red */ .row-trailer[data-state="none"] { border-style: dashed; color: var(--ink-faint); cursor: default; } .row-trailer[data-state="blocked"] { color: var(--signal-warn); cursor: default; } /* ---- add flow (inline, root and policy pre-filled) -------------------- */ .add-panel { margin: 0 0 var(--space-2); padding: var(--space-3) var(--space-3) var(--space-3); background: var(--panel); border: 1px solid var(--line); border-top: 0; border-radius: 0 0 var(--radius) var(--radius); box-shadow: inset 0 1px var(--highlight); } .add-roots { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: var(--space-2); } .root-option { display: grid; gap: var(--space-1); padding: var(--space-2) var(--space-3); font: inherit; text-align: left; color: var(--ink); background: var(--panel-raised); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: inset 0 1px var(--highlight); cursor: pointer; transition: border-color 150ms var(--ease-out); } .root-option:hover { border-color: var(--line-strong); } .root-option[aria-pressed="true"] { border-color: var(--accent); } .root-audience { font-family: var(--font-display); font-size: var(--text-md); font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; line-height: 1; } .root-policy, .root-path { font-size: var(--text-xs); } .add-actions { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-3) 0 0; } /* the one series-level add-time question (§4.1): the choice and what it does */ .add-autotrack { display: grid; justify-content: start; gap: var(--space-1); margin: var(--space-3) 0 0; } .add-note { margin: 0; font-size: var(--text-xs); color: var(--ink-muted); } .add-note[data-tone="fault"] { color: var(--signal-fault); } .add-done { margin: 0; color: var(--verdict-eligible); } /* ---- release deck (§9.3) ---------------------------------------------- */ .releases-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-3); margin: 0 0 var(--space-6); } .releases-id { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: var(--space-2); } .releases-title { margin: 0; font-family: var(--font-display); font-size: var(--text-lg); font-weight: 700; letter-spacing: 0.04em; line-height: 1.1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .releases-year { font-size: var(--text-xs); } /* ---- removal confirmation (§7.4, issue 104) --------------------------- */ /* remove is not the deck's errand: it reads as a quiet control until the operator reaches for it, and never competes with the sweep */ .control-quiet { color: var(--ink-muted); border-color: var(--line); } .control-quiet:hover, .control-quiet[aria-expanded="true"] { color: var(--accent-bright); border-color: var(--accent); } .remove-panel { margin: 0 0 var(--space-6); } .remove-body { display: grid; gap: var(--space-2); padding: var(--space-3); background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: inset 0 1px var(--highlight); } /* armed is a state, not a toggle: it is amber whenever files are on disk to delete */ .remove-body[data-armed="true"] { border-color: oklch(from var(--signal-warn) l c h / 55%); } .remove-evidence, .remove-path, .remove-note { margin: 0; font-size: var(--text-xs); } /* the folder is evidence: it wraps rather than truncating, because a path the operator cannot read whole is not a confirmation */ .remove-path { color: var(--ink-muted); overflow-wrap: anywhere; white-space: pre-line; } .remove-note { color: var(--ink-muted); } .remove-note[data-tone="warn"] { color: var(--signal-warn); } .remove-note[data-tone="fault"] { color: var(--signal-fault); } .remove-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-1) 0 0; } .remove-body[data-armed="true"] .remove-actions .control:first-child { color: var(--signal-warn); border-color: oklch(from var(--signal-warn) l c h / 55%); } .remove-body[data-armed="true"] .remove-actions .control:first-child:hover:not(:disabled) { border-color: var(--signal-warn); } /* the settings arm-then-confirm, shared with season and episode rows: an armed control turns caution amber until it disarms or fires */ .control[data-armed="true"] { color: var(--signal-warn); border-color: oklch(from var(--signal-warn) l c h / 55%); } .control[data-armed="true"]:hover:not(:disabled) { border-color: var(--signal-warn); } .bucket-head { align-items: center; } .bucket-toggle { margin-left: auto; min-height: var(--control-h); padding: 0 var(--space-2); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent); background: none; border: 0; cursor: pointer; transition: color 150ms var(--ease-out); } .bucket-toggle:hover { color: var(--accent-bright); } /* fixed-width mono columns: chips and header labels share rem widths, so the pseudo-table survives font-size differences and flex wrapping alike */ .cw { display: inline-block; flex: none; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; /* single nowrap line: line-height equal to the box height centres it, and keeps ellipsis + text-align working, which flex would break */ line-height: var(--control-h); } .cw-score { width: 4.5rem; text-align: right; } .cw-res { width: 4rem; } .cw-src { width: 4.5rem; } .cw-hdr { width: 6rem; } .cw-aud { width: 7.5rem; } .cw-size { width: 4.5rem; text-align: right; } .cw-seed { width: 3.5rem; text-align: right; } /* §9.3: headers never wrap — the narrow-deck block below sheds columns instead */ .colhead { display: flex; gap: var(--space-1); padding: var(--space-2) var(--space-1) 0; } .colhead .cw { padding: 0 var(--space-2); border: 1px solid transparent; font-family: var(--font-readout); font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-faint); } .rel { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1) var(--space-2); padding: var(--space-2) var(--space-1); border-bottom: 1px solid oklch(from var(--line) l c h / 45%); } /* the line is plain content now — grab is its own control */ .rel-line { flex: 1; min-width: 0; display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1); } /* §9.3: the release name is secondary — evidence, not the primary key */ .rel-name { flex-basis: 100%; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: var(--text-xs); color: var(--ink-faint); } .rel-none { padding: var(--space-2) var(--space-1); font-size: var(--text-xs); border-bottom: 1px solid oklch(from var(--line) l c h / 45%); } .rel-note { font-size: var(--text-xs); color: var(--ink-muted); } .rel-note[data-tone="fault"] { color: var(--signal-fault); } /* score heat: data-score is the list-relative stop, 0 lowest … 6 highest */ .rel-line .cw-score[data-score="0"] { color: var(--score-0); border-color: oklch(from var(--score-0) l c h / 45%); } .rel-line .cw-score[data-score="1"] { color: var(--score-1); border-color: oklch(from var(--score-1) l c h / 45%); } .rel-line .cw-score[data-score="2"] { color: var(--score-2); border-color: oklch(from var(--score-2) l c h / 45%); } .rel-line .cw-score[data-score="3"] { color: var(--score-3); border-color: oklch(from var(--score-3) l c h / 45%); } .rel-line .cw-score[data-score="4"] { color: var(--score-4); border-color: oklch(from var(--score-4) l c h / 45%); } .rel-line .cw-score[data-score="5"] { color: var(--score-5); border-color: oklch(from var(--score-5) l c h / 45%); } .rel-line .cw-score[data-score="6"] { color: var(--score-6); border-color: oklch(from var(--score-6) l c h / 45%); } /* ---- library view (§4.2) ---------------------------------------------- */ .rail-nav { flex: none; } /* the library's master line: total readout left, the one §4.2 toggle right */ .library-bar { margin: 0 0 var(--space-6); } /* the toggle's own padding would inset its text past the chip column's right rag; with the shape toggle right of it, SATISFIED sits flush against its neighbour instead of the deck edge */ .library-bar .bucket-toggle { padding: 0; margin-left: var(--space-2); } /* ---- library poster grid (§9.6, issue 151) ------------------------------ */ /* the shape toggle: a joined pair, engaged side lit like any view control */ .view-toggle { margin-left: auto; display: inline-flex; } .view-btn { min-height: var(--control-h); padding: 0 var(--space-2); font-family: var(--font-readout); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-faint); background: none; border: 1px solid var(--line); cursor: pointer; transition: border-color 150ms var(--ease-out), color 150ms var(--ease-out); } .view-btn + .view-btn { border-left-width: 0; } .view-btn:hover { color: var(--accent-bright); } .view-btn[aria-pressed="true"] { color: var(--accent-bright); border-color: var(--accent); } /* auto-fill keeps every tile the same width down to a phone, where three narrow-but-legible columns beat two oversized ones */ .deck-rows.grid-rows { display: grid; grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); gap: var(--space-4); } .card { display: flex; flex-direction: column; align-items: stretch; width: 100%; height: 100%; padding: 0; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; text-align: left; cursor: pointer; transition: border-color 150ms var(--ease-out), transform 150ms var(--ease-out); } .card:hover { border-color: var(--accent); } .card:active { transform: translateY(1px); } /* the poster is framed evidence, like every other image on a panel */ .card-art { display: block; aspect-ratio: 2 / 3; } .card-poster { display: block; width: 100%; height: 100%; object-fit: cover; background: var(--panel-raised); } /* same ratio as a poster, carrying the title: the grid never has a hole */ .card-blank { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; padding: var(--space-3); background: var(--panel-raised); box-shadow: inset 0 1px var(--highlight); } .card-blank-title { font-family: var(--font-display); font-size: var(--text-base); font-weight: 600; letter-spacing: 0.02em; text-align: center; color: var(--ink-faint); } .card-body { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-2); } .card-id { display: flex; align-items: baseline; gap: var(--space-1); min-width: 0; } .card-name { flex: 1; /* two clamped lines: a long title stays readable where a single ellipsised line at tile width would not be */ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; font-family: var(--font-display); font-size: var(--text-sm); font-weight: 600; letter-spacing: 0.02em; color: var(--ink); } .card-year { flex: none; font-size: var(--text-2xs); } .card-chips { display: flex; flex-wrap: wrap; gap: var(--space-1); } /* ---- series detail (§4.1 + §4.2, issue 129) --------------------------- */ /* one season: a collapsible group with its rule, counts and actions */ .season { border-bottom: 1px solid oklch(from var(--line) l c h / 45%); } .season-line { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-3); padding: var(--space-2) var(--space-1); } /* the disclosure is a drawn chevron — no glyph standing in for an icon */ .season-disclose { flex: none; width: 1.75rem; height: 1.75rem; display: inline-flex; align-items: center; justify-content: center; background: none; border: 1px solid var(--line); border-radius: var(--radius); cursor: pointer; transition: border-color 150ms var(--ease-out); } .season-disclose:hover, .season-disclose:focus-visible { border-color: var(--accent); } .season-disclose::before { content: ""; width: 0.4rem; height: 0.4rem; border-right: 2px solid var(--ink-muted); border-bottom: 2px solid var(--ink-muted); transform: rotate(-45deg) translate(-5%, -5%); transition: transform 200ms var(--ease-out); } .season-disclose[aria-expanded="true"]::before { transform: rotate(45deg) translate(-5%, -5%); } .season-name { font-family: var(--font-display); font-size: var(--text-base); font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; color: var(--ink); } /* the tracked toggle reads as the rule it is; pressed = on */ .season-line .control, .ep-actions .control { padding: 0 var(--space-3); } .season-space { flex: 1; } .episodes { margin: 0; padding: 0 0 var(--space-2) var(--space-8); list-style: none; } .episode { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1) var(--space-2); padding: var(--space-2) var(--space-1); border-bottom: 1px solid oklch(from var(--line) l c h / 45%); } .ep-tag { flex: none; width: 2.75rem; color: var(--ink-faint); } .ep-title { min-width: 0; font-size: var(--text-sm); color: var(--ink); } .ep-air { flex: none; width: 6.5rem; color: var(--ink-faint); } .ep-chips { margin-left: auto; } .ep-actions { display: flex; align-items: center; gap: var(--space-2); margin-left: var(--space-3); } .ep-unaired { font-size: var(--text-xs); } /* issue 122: gone upstream while its file remained — a conflict, amber dashed */ .chip[data-flag="vanished"] { color: var(--signal-warn); border-style: dashed; border-color: oklch(from var(--signal-warn) l c h / 55%); } /* ---- attention queues (§5.2 + §5.7) ------------------------------------ */ /* the badge is a signal, not a control: amber attention on the cyan word */ .nav-count { margin-left: var(--space-2); font-size: var(--text-xs); color: var(--signal-warn); } .queue-note { margin: var(--space-2) var(--space-1) 0; font-size: var(--text-xs); color: var(--ink-faint); } /* like .rel: the line expands to fill, the action is its own control */ .queue-item { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1) var(--space-3); padding: var(--space-2) var(--space-1); border-bottom: 1px solid oklch(from var(--line) l c h / 45%); } .queue-item .queue-line { flex: 1; min-width: 0; width: auto; padding: 0; border-bottom: 0; } .queue-empty { padding: var(--space-2) var(--space-1); font-size: var(--text-xs); border-bottom: 1px solid oklch(from var(--line) l c h / 45%); } .queue-feedback { font-size: var(--text-xs); color: var(--ink-muted); } .queue-feedback[data-tone="fault"] { color: var(--signal-fault); } /* ---- manual intake ---------------------------------------------------- */ .intake .module-detail { overflow-wrap: anywhere; } /* ---- title detail pages (§9.6, issues #149 and #150) ------------------- */ /* The hero is one machined module carrying what the title IS — shared by the movie and series pages, which is why its classes read `movie-*`. The backdrop hotlinks from image.tmdb.org (§9.6) and sinks into the console charcoal under a scrim, so the readouts keep their contrast — a photo never sits behind text bare. */ .movie-hero { position: relative; overflow: hidden; margin: 0 0 var(--space-8); } /* the backdrop layer: painted only when detail supplied one */ .movie-hero::before { content: ""; position: absolute; inset: 0; background-image: var(--backdrop); background-size: cover; background-position: center 20%; opacity: 0; transition: opacity 400ms var(--ease-out); } .movie-hero.has-backdrop::before { opacity: 1; } /* the scrim: ground at the bottom, heavy everywhere the text sits */ .movie-hero::after { content: ""; position: absolute; inset: 0; background: linear-gradient( to top, oklch(from var(--ground) l c h / 92%) 0%, oklch(from var(--ground) l c h / 72%) 45%, oklch(from var(--ground) l c h / 40%) 100% ), oklch(from var(--ground) l c h / 35%); } .movie-body { position: relative; z-index: 1; display: flex; gap: var(--space-6); align-items: flex-start; } /* the poster is framed evidence, like every other image on a panel */ .movie-poster { flex: none; width: 10.5rem; aspect-ratio: 2 / 3; object-fit: cover; border: 1px solid var(--line-strong); border-radius: var(--radius); box-shadow: 0 2px 12px oklch(from var(--lowlight) l c h / 75%); } .movie-info { min-width: 0; display: grid; gap: var(--space-2); padding: var(--space-6) 0; } .movie-idline { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2) var(--space-3); } /* the page's voice: bigger than a deck title, same display face */ .movie-title { margin: 0; font-family: var(--font-display); font-size: var(--text-xl); font-weight: 700; letter-spacing: 0.04em; line-height: 1.05; } .movie-year { font-size: var(--text-sm); } .movie-chips { margin-left: 0; } /* rating and meta are readouts on one line each; the star is drawn */ .movie-rating { display: inline-flex; align-items: center; gap: var(--space-1); margin: 0; color: var(--ink); } /* the star is amber (§4.2): rating reads at a glance, the number stays ink */ .star { width: 0.75rem; height: 0.75rem; fill: var(--signal-warn); } /* a row chip is smaller than the detail page's rating readout */ .row-rating .star { width: 0.625rem; height: 0.625rem; } .movie-meta { margin: 0; } .movie-tagline { margin: var(--space-1) 0 0; font-family: var(--font-display); font-size: var(--text-md); font-weight: 600; letter-spacing: 0.02em; color: var(--ink-muted); } .movie-overview { max-width: 65ch; margin: var(--space-1) 0 0; color: var(--ink); } .movie-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); } .movie-actions .add-note[data-tone="fault"] { margin: 0; color: var(--signal-fault); } /* external links wear the control skin; anchors must drop the underline */ .movie-actions a.control { text-decoration: none; } /* library controls follow the hero on their own row: state left, errands right */ .movie-controls { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-3); padding: var(--space-2) var(--space-1); } .movie-controls .control { padding: 0 var(--space-3); } /* wanted and blocked read as rules, pressed = on — the tracked-toggle idiom */ .movie-controls .control[aria-pressed="true"] { color: var(--accent-bright); border-color: var(--accent); } .movie-root-field { display: inline-flex; align-items: center; gap: var(--space-2); } .movie-root { width: auto; min-width: 11rem; } .movie-controls-space { flex: 1; } /* on-disk rows share the deck's rel grammar: name leads, chips trail */ .disk-name { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ink); } .rel[data-tone="fault"] { color: var(--signal-fault); } /* ---- small screens --------------------------------------------------- */ @media (max-width: 46rem) { .rail-inner { flex-wrap: wrap; gap: var(--space-3) var(--space-4); } /* the library bar wraps to two lines at phone widths: identity and count on the first, shape and satisfied toggles on the second, each label whole instead of squeezed into a mid-word break */ .library-bar { flex-wrap: wrap; row-gap: var(--space-2); } .library-bar .bucket-toggle, .view-btn { white-space: nowrap; } .rail-search { order: 4; flex-basis: 100%; max-width: none; } /* deterministic first row: identity, views, then meta pushed right — never re-wrapped by content or scrollbar changes */ .rail-nav { order: 2; } .rail-meta { order: 2; margin-left: auto; } .deck { padding: var(--space-6) var(--space-4) var(--space-8); } .row-chips { margin-left: 0; flex-basis: 100%; } /* an episode row wraps: identity first, chips full-width, actions right */ .ep-chips { margin-left: 0; flex-basis: 100%; } .ep-actions { margin-left: auto; } .episodes { padding-left: var(--space-4); } .releases-id { order: -1; flex-basis: 100%; } /* the line shrinks rather than wrapping, so the grab control never leaves the row — the release name absorbs the squeeze via ellipsis */ .rel-grab { margin-left: auto; } /* same wrap on a queue entry: the one click drops below its line */ .queue-item .queue-line { flex-basis: 100%; } .queue-allow { margin-left: auto; } /* the hero stacks: poster above the info block, both full width */ .movie-body { flex-direction: column; align-items: stretch; gap: var(--space-3); } .movie-poster { width: 8.5rem; } .movie-info { padding-bottom: var(--space-4); } /* state controls wrap to their own line, errands stay right-aligned */ .movie-controls-space { display: none; } .movie-root { min-width: 0; flex: 1; } .chain { grid-template-columns: 1fr; grid-template-areas: "tmdb" "prow" "t1" "master" "out" "sink"; row-gap: var(--space-4); } /* one junctioned trace on the stacked chain; the second is redundant */ .area-t2 { display: none; } .bus { height: 2.25rem; justify-content: center; } .bus svg { transform: rotate(90deg); transform-origin: center; width: 2.25rem; height: 0.5rem; } } /* ---- narrow deck (§9.3 at phone widths) ------------------------------- */ /* the full seven-column line is 36rem; under 40rem it cannot fit, so hdr and audio leave the line and the five surviving columns tighten — aligned, never wrapped, never scrolled */ @media (max-width: 40rem) { .colhead .cw, .rel-line .chip { padding: 0 var(--space-1); } .cw-hdr, .cw-aud { display: none; } .cw-score { width: 3rem; } .cw-res { width: 3rem; } .cw-src { width: 3.5rem; } .cw-size { width: 3rem; } .cw-seed { width: 2.5rem; } } /* ---- settings (issue #116) -------------------------------------------- */ /* rows follow the queues pattern: line, then an action pair */ .settings-row-actions { display: flex; gap: var(--space-2); margin: var(--space-1) 0 var(--space-2); } .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: var(--space-3); align-items: end; } .field { display: grid; gap: var(--space-1); } .field-label { font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: 0.12em; } .form-input { min-height: 2.25rem; width: 100%; padding: 0 var(--space-2); color: var(--ink); font-family: var(--font-readout); font-size: var(--text-xs); background: var(--ground); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: inset 0 1px var(--lowlight); caret-color: var(--accent); transition: border-color 150ms var(--ease-out); } .form-input:hover { border-color: var(--line-strong); } .form-input:focus { border-color: var(--accent); outline: none; } .form-input:disabled { color: var(--ink-faint); border-color: var(--line); } select.form-input { appearance: none; } .form-input[data-invalid] { border-color: var(--signal-fault); } /* one resolution's size band: label, floor, target, penalty on a line */ .band-line { display: flex; align-items: center; gap: var(--space-2); } .band-line .field-label { width: 3.5rem; } .band-num { width: 4.5rem; }