:root {
  --product-bg: oklch(16% .006 270);
  --product-surface: oklch(21% .006 270);
  --product-hover: oklch(27% .006 270);
  --product-line: oklch(33% .008 270);
  --product-text: oklch(96% .003 270);
  --product-muted: oklch(70% .008 270);
  --product-accent: oklch(61% .21 27);
  --product-header-height: 96px;
}
body { font-size: 18px; background: var(--product-bg); color: var(--product-text); }
#site-header { position: sticky; top: 0; z-index: 20; height: var(--product-header-height); min-height: var(--product-header-height); padding: 16px 30px; gap: 22px; background: var(--product-bg); border-bottom: 0; }
#site-header .logo { display: inline-flex; align-items: center; gap: 8px; color: var(--product-text); font-size: 28px; font-weight: 750; letter-spacing: -.9px; white-space: nowrap; text-decoration: none; }
#site-header .logo:focus-visible, .product-app a:focus-visible { outline: 3px solid var(--product-text); outline-offset: 4px; border-radius: 4px; }
/* The mark itself is the official inline YouTube SVG built in product.js
   (matching public/launcher.js's tile), not drawn with CSS. */
.product-mark { display: inline-flex; justify-content: center; align-items: center; }
.product-mark svg { width: 40px; height: auto; display: block; }
#site-status { margin: 0; padding: 12px 30px; background: oklch(28% .09 25); color: oklch(94% .02 25); font-size: 15px; font-weight: 650; }
#product-search-mount { flex: 1; display: flex; justify-content: center; min-width: 0; }
/* Groups Account/identity/Sign out as one right-aligned cluster, independent
   of whether the search bar in between is present or collapsed (mobile
   hides/collapses it when off /yt). The return-to-Theater icon (site-theater)
   is no longer part of this cluster: it sits at the start of the header,
   immediately left of the YouTube mark, in DOM/source order. */
#site-controls { display: flex; align-items: center; gap: 22px; margin-left: auto; }
.site-link { display: inline-flex; align-items: center; min-height: 44px; padding: 0 2px; color: var(--product-muted); font-size: 17px; font-weight: 650; text-decoration: none; white-space: nowrap; }
.site-link:hover { color: var(--product-text); }
.site-link:focus-visible { outline: 3px solid var(--product-text); outline-offset: 4px; border-radius: 4px; }
/* Icon-only "back to Theater" control. */
.site-icon-link { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: 10px; color: var(--product-muted); text-decoration: none; }
.site-icon-link:hover { color: var(--product-text); background: var(--product-hover); }
.site-icon-link:focus-visible { outline: 3px solid var(--product-text); outline-offset: 3px; }
.site-icon-link svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.8; }
#auth-identity { max-width: 200px; color: var(--product-muted); font-size: 16px; }
#site-header #auth-logout { white-space: nowrap; min-height: 56px; border-radius: 24px; }
#player-app, #stats, #log, #player-app .bar, #player-app .hint, #platform-ready { display: none !important; }
.product-app { display: grid; grid-template-columns: 240px minmax(0, 1fr); min-height: calc(100vh - var(--product-header-height)); }
.product-app button, .product-search button { font: inherit; cursor: pointer; color: inherit; }
.product-app button:disabled { cursor: default; opacity: .55; }
.product-app button:focus-visible, .product-search button:focus-visible { outline: 2px solid var(--product-text); outline-offset: 4px; }
.product-search input:focus-visible { outline: none; border-color: var(--product-muted); }
.product-nav { position: sticky; top: var(--product-header-height); align-self: start; padding: 12px 14px; display: flex; flex-direction: column; gap: 5px; }
.product-nav-item { display: flex; align-items: center; gap: 18px; min-height: 60px; padding: 0 17px; border: 0; border-radius: 11px; background: transparent; font-size: 18px; text-align: left; }
.product-nav-item:hover { background: var(--product-surface); }
.product-nav-item[aria-current=page] { background: var(--product-hover); font-weight: 650; }
.product-app svg, .product-search svg { width: 28px; height: 28px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
.product-nav-item:last-child { margin-top: 24px; }
#site-header .product-menu-button { display: grid; place-items: center; flex: 0 0 56px; width: 56px; height: 56px; padding: 12px; border: 0; border-radius: 50%; color: inherit; background: transparent; cursor: pointer; }
#site-header .product-menu-button:hover { background: var(--product-hover); }
.product-menu-button svg { width: 30px; height: 30px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.product-menu-button span, .product-nav-backdrop span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.product-nav-backdrop { position: fixed; inset: var(--product-header-height) 0 0; z-index: 18; border: 0; border-radius: 0; background: rgb(0 0 0 / .5); }
.product-content { width: 100%; min-width: 0; max-width: 1680px; margin: 0; padding: 20px 34px 52px; }
.product-heading { margin-bottom: 25px; }
.product-heading h1 { font-size: 34px; line-height: 1.3; font-weight: 700; letter-spacing: -.55px; }
.product-context { color: var(--product-muted); margin: 7px 0 0; font-size: 18px; max-width: 65ch; }
.product-search { display: flex; width: min(100%, 630px); height: 56px; border: 1px solid var(--product-line); border-radius: 28px; overflow: hidden; }
.product-search input { width: 100%; min-width: 0; padding: 0 20px; margin: 0; border: 0; background: oklch(13% .005 270); color: var(--product-text); font: inherit; font-size: 20px; outline: none; }
.product-search:focus-within { border-color: var(--product-text); }
.product-search-button { display: flex; justify-content: center; align-items: center; width: 66px; flex-shrink: 0; border: 0; border-left: 1px solid var(--product-line); background: var(--product-surface); }
.product-search-button:hover { background: var(--product-hover); }
.product-search-button span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.product-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 40px 26px; }
.product-video { display: block; width: 100%; min-width: 0; padding: 0; border: 0; background: transparent; text-align: left; border-radius: 13px; align-self: start; }
.product-thumbnail { display: block; width: 100%; padding: 0; border: 0; position: relative; aspect-ratio: 16 / 9; background: var(--product-surface); overflow: hidden; border-radius: 12px; }
.product-thumbnail img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform 160ms ease-out; }
.product-video:hover .product-thumbnail img { transform: scale(1.035); }
.product-live { background: oklch(52% .2 25 / .95); letter-spacing: .04em; }
.product-duration { position: absolute; right: 7px; bottom: 7px; padding: 1px 5px; border-radius: 4px; background: oklch(10% .004 270 / .9); color: var(--product-text); font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; }
.product-video-info { padding: 12px 1px 0; }
.product-video-info h2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin: 0 0 5px; font-size: 20px; font-weight: 650; line-height: 1.4; overflow-wrap: anywhere; }
.product-app .product-video-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; padding: 0; border: 0; background: transparent; text-align: left; font: inherit; }
.product-channel-link { color: inherit; text-decoration: none; }
.product-channel-link:hover { color: var(--product-text); }
.product-video-info p { margin: 2px 0 0; color: var(--product-muted); font-size: 16px; line-height: 1.5; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.product-resume-track { position: absolute; bottom: 0; left: 0; right: 0; height: 3px; background: var(--product-line); }
.product-resume-track span { display: block; min-width: 16px; height: 100%; background: var(--product-accent); }
.product-status { color: var(--product-muted); max-width: 65ch; margin: 0 0 18px; font-size: 16px; }
.product-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px; }
.product-actions:empty { display: none; }
.product-secondary, .product-tab { min-height: 56px; border: 1px solid var(--product-line); background: var(--product-surface); padding: 9px 18px; border-radius: 9px; font-weight: 600; }
.product-secondary:hover, .product-tab:hover { background: var(--product-hover); }
.product-tab[aria-pressed=true] { background: var(--product-text); color: var(--product-bg); }
.product-content > .product-secondary { margin-top: 32px; }
.product-sentinel { height: 1px; margin-top: 32px; }
.product-collections { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px 22px; margin-bottom: 30px; }
.product-collections:empty { display: none; }
.product-collection { display: flex; align-items: center; gap: 15px; min-height: 78px; padding: 15px; border: 1px solid var(--product-line); border-radius: 11px; background: transparent; text-align: left; }
.product-collection:hover { background: var(--product-surface); }
.product-collection span:last-child { overflow-wrap: anywhere; }
.product-avatar { display: grid; place-items: center; width: 56px; height: 56px; flex-shrink: 0; border-radius: 50%; background: var(--product-hover); font-size: 22px; }
.product-avatar { position: relative; overflow: hidden; }
.product-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.product-channel-heading { display: flex; align-items: center; gap: 25px; }
.product-heading-copy { min-width: 0; flex: 1; }
.product-channel-photo .product-avatar { width: 140px; height: 140px; font-size: 48px; }
.product-channel-heading h1 { font-size: clamp(28px, 3vw, 38px); }
.product-channel-heading .product-context { margin-top: 8px; }
.product-video-info:has(.product-card-avatar) { position: relative; padding-left: 56px; min-height: 56px; }
.product-card-avatar { position: absolute; left: 0; top: 13px; width: 44px; height: 44px; font-size: 17px; }
.product-card-avatar .product-avatar { width: 100%; height: 100%; font-size: inherit; }
.product-description { margin-top: 12px; padding: 13px 16px; background: var(--product-surface); border-radius: 10px; }
.product-description p { margin: 0; font-size: 18px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
.product-description .product-description-facts { font-weight: 650; color: var(--product-text); margin: 0 0 6px; white-space: normal; }
.product-description p[data-collapsed=true] { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }
.product-description-toggle { min-height: 56px; padding: 8px 0 0; border: 0; background: transparent; font-weight: 650 !important; }
.product-channel-heading .product-description { margin-top: 8px; padding: 0; background: transparent; max-width: 760px; }
.product-channel-identity { display: flex; flex-direction: column; gap: 4px; text-align: left; }
.product-channel-identity small { font-weight: 400; color: var(--product-muted); font-size: 16px; }
.product-channel-collection { flex-wrap: wrap; }
.product-channel-collection small { flex-basis: 100%; margin: 0 0 0 71px; }
.product-skeleton { min-width: 0; }
.product-skeleton div { aspect-ratio: 16 / 9; border-radius: 12px; background: var(--product-surface); }
.product-skeleton span { display: block; height: 13px; width: 83%; margin-top: 15px; border-radius: 4px; background: var(--product-surface); }
.product-skeleton span:last-child { width: 54%; height: 10px; margin-top: 10px; }
.product-watch { display: grid; grid-template-columns: minmax(0, 1fr) 350px; gap: 26px; }
.product-viewing { min-width: 0; }
.product-player { position: relative; min-width: 0; aspect-ratio: 16 / 9; max-height: max(0px, calc(100vh - var(--product-header-height) - var(--watch-title-height, 80px) - 64px)); max-height: max(0px, calc(100dvh - var(--product-header-height) - var(--watch-title-height, 80px) - 64px)); border-radius: 12px; background: var(--product-bg); }
.product-player-notice { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 24px; text-align: center; border-radius: inherit; background: var(--product-surface); }
.product-player-notice p { margin: 0; }
.product-player[data-loading=true] .product-player-notice { background: transparent; pointer-events: none; }
.product-player[data-loading=true] #screen, .product-player[data-loading=true] .center-btn { visibility: hidden; }
.product-player[data-loading=true] .controls { opacity: 1; visibility: visible; pointer-events: none; }
.product-player[data-loading=true] #fs { pointer-events: auto; }
.product-player:fullscreen { width: 100%; height: 100%; max-height: none; aspect-ratio: auto; border-radius: 0; }
.product-player .stage { height: 100%; border: 0; border-radius: 12px; }
.product-player .screen-wrap { height: 100%; aspect-ratio: auto; }
.product-player .meta { display: none; }
/* The same overlay control sizes on both players (YouTube and Plex share the stage). */
.product-player .controls, .plex-player .controls { gap: 15px; padding: 15px 20px 20px; }
.product-player .controls button, .plex-player .controls button { align-items: center; justify-content: center; width: 70px; height: 70px; flex: 0 0 70px; padding: 12.5px; }
.product-player .controls button svg, .plex-player .controls button svg { width: 40px; height: 40px; }
.product-player .controls .t, .plex-player .controls .t { font-size: 22.5px; min-width: 72.5px; }
.product-player .controls .seek, .plex-player .controls .seek { height: 70px; min-width: 0; }
.product-player .seek::-webkit-slider-runnable-track, .plex-player .seek::-webkit-slider-runnable-track { height: 10px; border-radius: 5px; }
.product-player .seek::-webkit-slider-thumb, .plex-player .seek::-webkit-slider-thumb { width: 32.5px; height: 32.5px; margin-top: -11.25px; }
.product-player .seek::-moz-range-track, .product-player .seek::-moz-range-progress, .plex-player .seek::-moz-range-track, .plex-player .seek::-moz-range-progress { height: 10px; border-radius: 5px; }
.product-player .seek::-moz-range-thumb, .plex-player .seek::-moz-range-thumb { width: 32.5px; height: 32.5px; }
.product-player .center-btn, .plex-player .center-btn { width: 100px; height: 100px; }
.product-player .center-btn svg, .plex-player .center-btn svg { width: 52px; height: 52px; fill: currentColor; stroke: none; }
.product-player .center-btn.replay svg, .plex-player .center-btn.replay svg { fill: none; stroke: currentColor; stroke-width: 2; }
.product-player .preview { bottom: 115px; }
.product-player .preview-time { font-size: 20px; }
.product-player .captions { bottom: 125px; }
.product-player .captions span { font-size: clamp(20px, 2vw, 28px); }
.product-player .cc-menu, .plex-player .cc-menu { bottom: 115px; max-height: 50%; min-width: 250px; }
.product-player .cc-menu button, .plex-player .cc-menu button { min-height: 70px; padding: 11.25px 15px; font-size: 22.5px; }
.product-player .skip-hint { width: 120px; height: 120px; font-size: 17.5px; }
.product-player .skip-hint svg { width: 37.5px; height: 37.5px; }
.product-player .skip-marker-btn, .plex-player .skip-marker-btn { right: 20px; bottom: 100px; padding: 14px 24px; border-radius: 10px; font-size: 17.5px; }
.auth-panel button, #site-header button { font: inherit; }
.auth-panel button { min-height: 56px; }
.product-watch-title { font-size: 28px; line-height: 1.4; margin: 18px 0 10px; font-weight: 650; overflow-wrap: anywhere; }
.product-channel { display: inline-flex; min-height: 56px; align-items: center; gap: 12px; padding: 0; background: transparent; border: 0; font-weight: 600 !important; }
.product-channel:hover:not(:disabled) { text-decoration: underline; text-underline-offset: 4px; }
.product-channel:disabled { opacity: 1 !important; }
.product-next-heading { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 16px; }
.product-next-heading h2 { font-size: 21px; font-weight: 650; margin: 0; }
/* The Up next countdown: an overlay near the bottom of the player, above
   the controls, the same spot and look as the Plex player's own notice. */
.product-countdown { position: absolute; left: 50%; bottom: 84px; transform: translateX(-50%); z-index: 8; display: flex; align-items: center; gap: 16px; max-width: calc(100% - 32px);
                     padding: 10px 12px 10px 18px; border-radius: 12px; background: rgba(18,18,20,.92); border: 1px solid #303034; box-shadow: 0 8px 24px rgba(0,0,0,.5); }
.product-countdown[hidden] { display: none; }
.product-countdown p { margin: 0; color: #fff; font-size: 17px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.product-countdown .product-secondary { min-height: 40px; padding: 0 14px; }
.product-autoplay { display: flex; align-items: center; min-height: 56px; gap: 12px; color: var(--product-muted); font-size: 17px; cursor: pointer; }
/* Inside the player's overlay controls: compact, white, sits with the buttons. */
.controls .product-autoplay { position: relative; min-height: 0; gap: 0; margin: 0 4px 0 6px; color: #fff; font: 600 13px/1 -apple-system, system-ui, sans-serif; white-space: nowrap; }
/* While an Up next countdown is showing, the replay button would only
   compete with it: hidden until the countdown ends or is cancelled. */
/* A broadcast: the duration label reads LIVE (public/player.js) and there is no timeline to drag. */
.stage[data-live="true"] #dur { color: oklch(72% .2 25); font-weight: 700; letter-spacing: .04em; cursor: pointer; }
.stage[data-live="true"][data-live-behind="true"] #dur { color: oklch(78% 0 0); font-weight: 600; letter-spacing: 0; }
.stage[data-live-no-timeline="true"] #seek, .stage[data-live-no-timeline="true"] #cur { display: none; }
/* Without the scrubber (the controls' flexible middle), LIVE stays at the
   left and the buttons keep the right edge. */
.stage[data-live-no-timeline="true"] #dur { cursor: default; margin-right: auto; }
.stage[data-up-next="true"] .center-btn { display: none; }
/* The concurrent-screens screen over the picture, while the player waits for a slot (public/player.js's admissionNotice). */
.screen-limit { position: absolute; inset: 0; z-index: 8; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 24px; background: rgb(0 0 0 / .78); color: #fff; text-align: center; }
.screen-limit[hidden] { display: none; }
.screen-limit-title { margin: 0; font-size: 26px; font-weight: 750; }
.screen-limit-text { margin: 0; font-size: 19px; }
.screen-limit-hint { margin: 6px 0 0; font-size: 16px; color: oklch(80% .01 270); max-width: 46ch; }
/* The day's-allowance reminder over the picture (public/player.js's limitReminder). */
.limit-reminder { position: absolute; top: 16px; left: 16px; z-index: 8; display: flex; align-items: center; gap: 12px; max-width: min(90%, 520px); padding: 10px 10px 10px 16px; border-radius: 12px; background: rgb(0 0 0 / .72); color: #fff; font-size: 17px; font-weight: 600; text-shadow: none; }
.limit-reminder[hidden] { display: none; }
.limit-reminder button { display: grid; place-items: center; width: 36px; height: 36px; border: 0; border-radius: 8px; background: rgb(255 255 255 / .12); color: #fff; font-size: 16px; cursor: pointer; }
.limit-reminder button:hover { background: rgb(255 255 255 / .22); }
/* Nor the skip button: once Up next is counting down, skipping the credits
   it is counting through has nothing left to offer, and the two controls
   compete for the same corner of the picture. */
.stage[data-up-next="true"] #skipMarker { display: none; }
/* Every overlay control names itself the same way: a small tooltip above
   it on hover/focus (the Autoplay switch's word, and each button's data-tip). */
.controls button[data-tip] { position: relative; }
.controls button[data-tip]::after { content: attr(data-tip); position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%); padding: 5px 8px; border-radius: 6px;
                                    background: rgba(18,18,20,.96); border: 1px solid #303034; color: #fff; font: 600 13px/1 -apple-system, system-ui, sans-serif; white-space: nowrap;
                                    opacity: 0; visibility: hidden; transition: opacity .15s, visibility .15s; pointer-events: none; }
.controls button[data-tip]:hover::after, .controls button[data-tip]:focus-visible::after { opacity: 1; visibility: visible; }
/* Not while its menu is open, where it would sit over the menu. */
.stage:has(#ccMenu:not([hidden])) #cc::after, .stage:has(#audioMenu:not([hidden])) #audio::after { opacity: 0; visibility: hidden; }
.controls .product-autoplay span { position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%); padding: 5px 8px; border-radius: 6px;
                                   background: rgba(18,18,20,.96); border: 1px solid #303034; opacity: 0; visibility: hidden; transition: opacity .15s, visibility .15s; pointer-events: none; }
.controls .product-autoplay:hover span, .controls .product-autoplay:focus-within span { opacity: 1; visibility: visible; }
.controls .product-autoplay input { width: 36px; height: 20px; padding: 3px; background: rgba(255,255,255,.35); }
.controls .product-autoplay input::before { width: 14px; height: 14px; background: #fff; }
.controls .product-autoplay input:checked { background: var(--player-accent, #e8a90c); }
.controls .product-autoplay input:checked::before { transform: translateX(16px); background: #fff; }
.product-autoplay input { appearance: none; width: 44px; height: 28px; margin: 0; padding: 4px; border: 0; border-radius: 16px; background: var(--product-line); cursor: pointer; }
.product-autoplay input::before { content: ''; display: block; width: 20px; height: 20px; border-radius: 50%; background: var(--product-muted); transition: transform 160ms ease-out; }
.product-autoplay input:checked { background: var(--product-text); }
.product-autoplay input:checked::before { transform: translateX(16px); background: var(--product-bg); }
.product-next-list { display: flex; flex-direction: column; gap: 28px; }
.product-account { max-width: 650px; }
.product-account-email { font-size: 17px; margin: 0 0 30px; overflow-wrap: anywhere; }
.product-account #youtube-connection { margin: 0; padding: 0; border: 0; background: transparent; }

/* Theater launcher ('/'): a dark, restrained, TV-friendly app grid. YouTube
   is the only working destination; the rest are visibly disabled. */
.theater-app { max-width: 1080px; margin: 0 auto; padding: 64px 34px 90px; min-height: calc(100vh - var(--product-header-height)); }
.theater-heading { margin-bottom: 44px; text-align: center; }
.theater-more { margin: 36px 0 0; text-align: center; color: var(--product-muted); font-size: 18px; }
.theater-heading h1 { font-size: 42px; font-weight: 700; letter-spacing: -.6px; margin: 0; }
.theater-heading p { color: var(--product-muted); margin: 0; font-size: 18px; max-width: 60ch; }
.theater-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px; margin: 0; padding: 0; }
/* Wide, screen-like tiles: identity centered instead of top-left icon+heading. */
.theater-tile { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
                aspect-ratio: 16 / 9; min-height: 0; padding: 20px; border-radius: 16px;
                border: 1px solid var(--product-line); background: var(--product-surface); text-align: center; color: inherit; }
.theater-tile-active:hover { background: var(--product-hover); }
.theater-tile:focus-visible { outline: 3px solid var(--product-text); outline-offset: 4px; }
/* Disabled tiles stay readable: a slightly recessed fill, not a dimmed/opacity-faded card. */
.theater-tile:disabled { background: oklch(19% .005 270); cursor: default; }
.theater-tile-identity { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.theater-tile-mark { width: 64px; height: 45px; color: var(--product-accent); flex-shrink: 0; }
.theater-tile:disabled .theater-tile-mark { color: var(--product-muted); }
.theater-tile-mark-official { width: 120px; height: 84px; }
.theater-tile-label { font-size: 21px; font-weight: 700; letter-spacing: -.2px; }
.theater-tile:disabled .theater-tile-label { color: var(--product-muted); }
/* Services without a drawn mark get a recognizable typographic wordmark
   treatment instead, kept neutral grayscale since they are unavailable. */
@media (max-width: 900px) { .theater-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) {
  .theater-app { padding: 36px 18px 60px; } .theater-heading h1 { font-size: 32px; } .theater-grid { grid-template-columns: 1fr; }
  .theater-tile { aspect-ratio: auto; min-height: 148px; }
}

/* Plex surface ('/plex'): a fully separate vertical from the YouTube app,
   sharing only the generic .site-icon-link control style for its own
   return-to-Theater link. Mirrors Plex's own browse layout: a full-bleed,
   dense poster grid under a sticky top bar (return control, library tabs,
   search, a connection indicator) — the library is the first thing visible
   once connected. Server chooser, Disconnect and the Plex Pass requirement
   live in a panel revealed from that indicator, not at the top of the page;
   before connecting there is no library yet, so that flow (QR/connect/
   status) stays the primary, centered view instead. */
/* Plex's own palette (Plex Web's dark theme), scoped to the Plex route only
   so it overrides the product's generic accent/surface variables (used
   throughout this file via var(--product-*)) without touching the YouTube
   surface at all: gold replaces red as the accent (progress bars, the
   active sidebar item, the Play button), and the neutrals shift to Plex
   Web's own dark greys. `--product-accent-ink` is the Play button's own
   text color (dark-on-gold here; falls back to near-white everywhere else
   -- see the generic `.plex-primary` rule above). `--player-accent`
   likewise retints the reused-as-is player.js controls inside '.stage'
   while on this route (see index.html's own ':root' default). */
body[data-route="plex"] {
  --product-accent: #E5A00D;
  --product-accent-hover: #F2B22A;
  --product-accent-ink: #191A1C;
  --product-bg: #191A1C;
  --product-surface: #282A2D;
  --product-hover: #35373C;
  --product-line: #3A3C41;
  --product-text: #EEEEEE;
  --product-muted: #8C8C8C;
  --player-accent: #E5A00D;
}
body[data-route="plex"] .plex-primary:hover:not(:disabled) { background: var(--product-accent-hover); filter: none; }
.plex-app { width: 100%; margin: 0; padding: 0 0 90px; }
/* The watch page is exactly one screen tall: no bottom gutter, no scroll. */
.plex-app:has(.plex-watch:not([hidden])) { padding-bottom: 0; }
/* Once the shared stage is mounted its own spinner takes over; the page's
   placeholder spinner must not show alongside it. */
.plex-player:has(.stage) [data-plex="player-spinner"] { display: none; }
.plex-app button, .plex-app a { font: inherit; cursor: pointer; color: inherit; }
.plex-app button:disabled { opacity: .55; cursor: default; }
.plex-app button:focus-visible, .plex-app a:focus-visible { outline: 3px solid var(--product-text); outline-offset: 4px; }
.plex-topbar { position: sticky; top: var(--product-header-height); z-index: 10; background: var(--product-bg); border-bottom: 1px solid var(--product-line); }
.plex-topbar-row { display: flex; align-items: center; gap: 16px; padding: 16px 34px 14px; }
.plex-topbar .site-icon-link { width: 44px; height: 44px; flex-shrink: 0; }
.plex-topbar .site-icon-link svg { width: 22px; height: 22px; }
.plex-brand { display: inline-flex; flex-shrink: 0; color: var(--product-text); }
.plex-brand svg { width: 28px; height: 28px; }
/* Same height as the shared header beside it, divider on the same line. */
.plex-sidebar-head { display: flex; align-items: center; gap: 10px; height: var(--product-header-height); min-height: var(--product-header-height); box-sizing: border-box; padding: 0 12px; border-bottom: 1px solid var(--product-line); margin-bottom: 10px; flex-shrink: 0; }
.plex-sidebar-head .site-icon-link { width: 40px; height: 40px; flex-shrink: 0; }
.plex-sidebar-head .site-icon-link svg { width: 20px; height: 20px; }
.plex-sidebar-head .plex-brand svg { width: 112px; height: 112px; margin: -34px 0; }
/* The search box itself lives in the shared '#product-search-mount' in
   #site-header, the same spot youtube.js's own search uses -- not in this
   row -- so switching between /yt and /plex keeps one header height. Plex
   Web-style: a wide field left-aligned at the content gutter (not centered
   like /yt's), roughly 40% of the header's width between 320-560px -- see
   the '#product-search-mount' justify-content override further down. */
#product-search-mount .plex-search { width: clamp(320px, 40vw, 560px); height: 48px; }

/* ---- Sidebar (Plex Web layout): server switcher, Home, the library
   section list, and the connection/settings affordance at the bottom. Fixed
   width on desktop, an icon-only rail on narrower/TV widths, a horizontal
   stack at the very top on phones (see the media queries below).
   position:fixed (not sticky under the header) so it runs the full page
   height from the very top edge, like Plex Web's own left nav -- above
   #site-header's own z-index, so it visually occludes the header's left
   edge instead of sitting below it; the header's own content and the
   content column are then offset right by the sidebar's width (see the
   'body:has(.plex-sidebar...)' rules at the end of this file), so the
   header only ever appears to span the column to the right of the sidebar.
   No horizontal padding of its own -- children carry their own 12px inset
   instead -- so every divider border below (and this rule's own
   border-right) runs the full sidebar width, edge to edge. */
.plex-shell { display: flex; align-items: flex-start; width: 100%; }
.plex-sidebar { flex: 0 0 240px; width: 240px; position: fixed; top: 0; left: 0; z-index: 22;
                display: flex; flex-direction: column; gap: 4px; height: 100vh; height: 100dvh;
                border-right: 1px solid var(--product-line); padding: 0 0 18px; overflow-y: auto; background: var(--product-bg); }
.plex-sidebar[hidden] { display: none; }
.plex-sidebar-server { padding: 6px 12px; border-bottom: 1px solid var(--product-line); margin-bottom: 6px; }
.plex-sidebar-server[hidden] { display: none; }
.plex-server-select { width: 100%; min-height: 44px; padding: 0 12px; border: 1px solid var(--product-line); border-radius: 10px;
                       background: var(--product-surface); color: var(--product-text); font: inherit; font-weight: 650; }
.plex-server-select[hidden] { display: none; }
/* Same 44px row as the server select it stands in for, text centred in it. */
.plex-server-label { display: flex; align-items: center; justify-content: center; text-align: center; min-height: 44px; padding: 0 2px; font-weight: 650; color: var(--product-muted); font-size: 15px; }
.plex-server-label[hidden] { display: none; }
.plex-sidebar-nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }
/* Plex Web's own sidebar selection: a square, edge-to-edge highlight band
   (no inset, no rounded corners -- the sidebar itself carries no horizontal
   padding, see its own rule above, so width:100% already reaches both
   edges) with a gold left indicator bar on the active item. */
.plex-nav-item { position: relative; display: flex; align-items: center; gap: 14px; min-height: 44px; padding: 0 12px; border: 0; border-radius: 0;
                  background: transparent; color: var(--product-muted); font-size: 15px; font-weight: 650; text-align: left; width: 100%; }
.plex-nav-item svg { width: 20px; height: 20px; flex-shrink: 0; }
.plex-nav-item span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plex-nav-item:hover { background: var(--product-hover); color: var(--product-text); }
.plex-nav-item[aria-current=true] { background: var(--product-hover); color: var(--product-accent); }
.plex-nav-item[aria-current=true]::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--product-accent); }
.plex-sidebar-sections { display: flex; flex-direction: column; gap: 2px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--product-line); }
.plex-sidebar-bottom { margin-top: auto; padding-top: 12px; border-top: 1px solid var(--product-line); }
/* The Settings page ('/plex/settings'): skip intros/credits, the playback
   preferences that used to live only inside the player, and the connection
   panel (server chooser lives in the sidebar's own row; this holds
   Disconnect and the Plex Pass requirement) that used to be an inline
   accordion off the sidebar's Settings item. Same padding convention as
   .plex-detail/.plex-home. */
.plex-settings[hidden] { display: none; }
.plex-settings { padding: 24px 34px 60px; max-width: 720px; }
.plex-settings-section { margin: 0 0 40px; }
.plex-settings-section:last-child { margin-bottom: 0; }
.plex-settings-heading { font-size: 19px; font-weight: 700; margin: 0 0 8px; }
.plex-settings-section > .plex-muted { margin: 0 0 18px; }
.plex-skip-group { display: flex; flex-direction: column; gap: 10px; }
.plex-skip-option { display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; border: 1px solid var(--product-line); border-radius: 10px; cursor: pointer; }
.plex-skip-option:hover { background: var(--product-hover); }
.plex-skip-option:has(input:checked) { border-color: var(--product-accent); background: var(--product-hover); }
.plex-skip-option input { margin: 3px 0 0; width: 18px; height: 18px; accent-color: var(--product-accent); flex-shrink: 0; }
.plex-skip-option-text { display: flex; flex-direction: column; gap: 2px; }
.plex-skip-option-label { font-weight: 650; }
.plex-skip-option-desc { color: var(--product-muted); font-size: 14px; }
.plex-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--product-line); }
.plex-settings-row:last-child { border-bottom: 0; }
.plex-settings-row-label { font-weight: 600; }
.plex-quality-select { min-height: 44px; padding: 0 12px; border: 1px solid var(--product-line); border-radius: 8px; background: var(--product-surface); color: var(--product-text); font: inherit; font-weight: 650; }
.plex-settings .product-autoplay { padding: 12px 0; }
.plex-connection-panel { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.plex-connection-panel .plex-requirement { margin: 14px 0 0; font-size: 14px; }
.plex-connection-panel .plex-secondary { min-width: 160px; }
.plex-primary, .plex-secondary { min-height: 52px; padding: 0 22px; border-radius: 10px; font-weight: 650; }
.plex-primary { border: 0; background: var(--product-accent); color: var(--product-accent-ink, oklch(98% .002 270)); }
.plex-primary:hover:not(:disabled) { filter: brightness(1.12); }
.plex-secondary { border: 1px solid var(--product-line); background: transparent; color: inherit; }
.plex-secondary:hover:not(:disabled) { background: var(--product-hover); }
/* Pre-connect flow (every phase except 'connected'): the only content on
   the page until there is a library to show, so it stays a centered,
   readable column instead of the full-bleed grid used once connected. */
.plex-connect { max-width: 560px; margin: 0 auto; padding: 56px 34px 40px; }
.plex-connect[hidden] { display: none; }
.plex-connection { width: 100%; }
.plex-requirement { color: var(--product-muted); font-size: 15px; line-height: 1.6; max-width: 62ch; margin: 0 0 22px;
                     padding: 14px 18px; border: 1px solid var(--product-line); border-radius: 12px; background: var(--product-bg); }
.plex-status { min-height: 24px; margin: 0 0 18px; font-size: 17px; }
.plex-pending { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
.plex-qr { display: block; width: 220px; height: 220px; max-width: 100%; background: #fff; border-radius: 8px; }
.plex-main { flex: 1; min-width: 0; }
.plex-library[hidden] { display: none; }
.plex-library { padding: 4px 34px 40px; }
.plex-container { padding: 4px 34px 40px; }
.plex-muted { color: var(--product-muted); font-size: 16px; margin: 0; }
.plex-section-heading { font-size: 24px; font-weight: 700; letter-spacing: -.3px; margin: 0 0 22px; }
/* Library header: title, item count and the sort control together. */
.plex-browse-head { display: flex; align-items: center; flex-wrap: wrap; gap: 14px 22px; margin-bottom: 22px; }
.plex-browse-head .plex-section-heading { margin: 0; }
.plex-browse-controls { display: flex; align-items: center; gap: 14px; margin-left: auto; }
.plex-item-count { color: var(--product-muted); font-size: 14px; white-space: nowrap; }
/* The sort control: one pill -- the field (a native select drawn as plain
   text with its own chevron) and the direction arrow beside it. */
.plex-sort { display: inline-flex; align-items: center; height: 40px; border: 1px solid var(--product-line); border-radius: 20px; background: var(--product-surface); overflow: hidden; }
.plex-sort-select { appearance: none; -webkit-appearance: none; height: 40px; min-height: 0; padding: 0 34px 0 16px; border: 0; border-radius: 0; cursor: pointer;
                     background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right 12px center / 16px 16px;
                     color: var(--product-text); font: inherit; font-size: 14px; font-weight: 650; }
.plex-sort-select:hover { background-color: var(--product-hover); }
.plex-sort-select:focus-visible { outline: none; background-color: var(--product-hover); }
.plex-sort-direction { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; border: 0; border-left: 1px solid var(--product-line); border-radius: 0;
                        background: transparent; color: var(--product-muted); }
.plex-sort-direction svg { width: 16px; height: 16px; }
.plex-sort-direction:hover { background: var(--product-hover); color: var(--product-text); }
.plex-sort-direction[hidden] { display: none; }

/* ---- Home ('/plex'): Continue Watching, then one "Recently Added in
   <library>" row per section, each scrolling horizontally with a hidden
   scrollbar (Plex Web's own convention). */
.plex-home[hidden] { display: none; }
.plex-home { padding: 24px 34px 40px; }
.plex-home-body { display: flex; flex-direction: column; gap: 34px; }
.plex-home-row-head { display: flex; align-items: baseline; gap: 16px; margin-bottom: 14px; }
.plex-home-row-title { font-size: 19px; font-weight: 700; margin: 0; }
.plex-home-row-link { margin-left: auto; color: var(--product-muted); font-size: 14px; font-weight: 650; white-space: nowrap; }
.plex-home-row-link:hover { color: var(--product-text); }
/* overflow-x: auto forces overflow-y to clip too (the CSS overflow spec
   computes a lone 'visible' axis as 'auto' once the other axis isn't
   'visible'), which was cutting off the top of a card's hover/focus lift
   (.plex-item:hover .plex-item-thumb below translates up 6px plus a
   shadow) right under the row heading. The extra top padding gives that
   lift room to clear the scroller's own clipped edge; the matching
   negative margin cancels the padding's visual effect on row spacing. */
.plex-home-scroller { display: flex; gap: 18px; overflow-x: auto; padding: 10px 2px 6px; margin: -10px -2px 0; scrollbar-width: none; }
.plex-home-scroller::-webkit-scrollbar { display: none; }
.plex-home-scroller .plex-item { flex: 0 0 170px; }
/* Library search: built once and never torn down by a render, so a large,
   obvious focus ring and hit target matter more than usual on a TV-like
   display that is often driven without a keyboard. */
.plex-search { display: flex; align-items: center; gap: 8px; width: 100%; height: 48px;
               border: 1px solid var(--product-line); border-radius: 24px; background: oklch(13% .005 270); padding: 0 6px 0 16px; }
.plex-search[hidden] { display: none; }
.plex-search:focus-within { border-color: var(--product-text); }
.plex-search-icon { display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--product-muted); }
.plex-search-icon svg { width: 19px; height: 19px; }
.plex-search input { flex: 1; min-width: 0; border: 0; background: transparent; color: var(--product-text); font: inherit; font-size: 16px; outline: none; }
.plex-search-clear { flex-shrink: 0; min-height: 38px; padding: 0 14px; border: 0; border-radius: 19px; background: transparent; color: var(--product-muted); font-weight: 650; }
.plex-search-clear:hover { background: var(--product-hover); color: var(--product-text); }
.plex-search-clear:focus-visible { outline: 2px solid var(--product-text); outline-offset: 3px; }
.plex-search input:focus-visible { outline: none; border-color: var(--product-muted); }
/* The search dropdown: a poster on the left, title + one muted meta line on
   the right (the exact same card text as everywhere else -- cardTitle()/
   cardMeta() in plex.js), scrollable -- mirrors youtube.js's own
   '.product-suggestions' dropdown so the two searches feel like one system. */
/* The results sit under the box and share its width exactly (the box is
   left-aligned in the mount, so the dropdown takes the box's own width). */
.plex-search-results { position: absolute; top: calc(100% + 8px); left: 0; width: clamp(320px, 40vw, 560px); max-width: 100%; z-index: 30; max-height: min(70vh, 560px); overflow-y: auto;
                        padding: 8px; border: 1px solid var(--product-line); border-radius: 16px; background: var(--product-surface); box-shadow: 0 8px 28px rgb(0 0 0 / .35); }
.plex-search-results[hidden] { display: none; }
.plex-search-results > p { margin: 0; padding: 14px; color: var(--product-muted); }
.plex-search-result { display: flex; align-items: center; gap: 14px; width: 100%; padding: 8px; border: 0; border-radius: 10px; background: transparent; color: var(--product-text); text-align: left; cursor: pointer; }
.plex-search-result:hover, .plex-search-result[aria-selected=true] { background: var(--product-hover); }
.plex-search-result-thumb { position: relative; flex: 0 0 auto; overflow: hidden; display: flex; align-items: center; justify-content: center; width: 46px; aspect-ratio: 2 / 3;
                             border-radius: 6px; background: var(--product-hover); color: var(--product-muted); font-size: 13px; font-weight: 700; }
.plex-search-result-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.plex-search-result-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.plex-search-result-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 650; }
.plex-search-result-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--product-muted); font-size: 13px; }
.plex-library-message { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; }
/* Dense, Plex-like poster grid: sized by minmax so a wide TV-like display
   fits many more posters per row than a fixed column count would. */
.plex-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 30px 20px; margin-bottom: 24px; }
.plex-item { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; padding: 0; border: 0; background: transparent; text-align: left; border-radius: 12px; }
.plex-item-thumb { position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; width: 100%; aspect-ratio: 2 / 3;
                    border-radius: 8px; background: var(--product-surface); color: var(--product-muted); font-size: 28px; font-weight: 700;
                    transition: transform 160ms ease, box-shadow 160ms ease; }
/* Plex-like hover/focus: the poster lifts and brightens; the tile's own
   focus-visible outline (see .plex-app button:focus-visible above) stays
   the clear, high-contrast focus ring. */
.plex-item:hover .plex-item-thumb, .plex-item:focus-visible .plex-item-thumb { transform: translateY(-6px); box-shadow: 0 16px 32px rgb(0 0 0 / .5); background: var(--product-hover); color: var(--product-text); }
/* Same-origin art relayed through /api/v1/plex/art/<ratingKey> (see plex.js's
   artURL()); layered over the initials placeholder, which stays in the DOM
   underneath as the fallback while loading or on a silent 404/error. */
.plex-item-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: filter 160ms ease; }
.plex-item:hover .plex-item-art, .plex-item:focus-visible .plex-item-art { filter: brightness(1.1); }
/* Same visual convention as .product-resume-track, scaled to the poster tile. */
.plex-item-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: rgb(0 0 0 / .55); }
.plex-item-progress span { display: block; min-width: 10px; height: 100%; background: var(--product-accent); }
/* A finished free-catalog title: full bar plus a check in the corner
   (the "Free" marker sits in the other corner). */
.plex-item-progress-done span { min-width: 100%; }
.plex-item-watched { position: absolute; top: 6px; right: 6px; z-index: 1; display: inline-flex; align-items: center; justify-content: center;
                     width: 22px; height: 22px; border-radius: 50%; background: var(--product-accent); color: var(--product-accent-ink, oklch(98% .002 270));
                     font-size: 13px; font-weight: 800; line-height: 1; }
/* Fixed text heights (two title lines, one meta line) so every card in a
   row or grid is the same height whatever its text, and a placeholder card
   is exactly the size of the real one that replaces it. */
.plex-item-text { display: block; width: 100%; min-height: calc(10px + 2 * 1.35 * 15px + 1.4 * 13px); }
.plex-item-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-top: 10px; font-size: 15px; font-weight: 650; line-height: 1.35; width: 100%; }
.plex-item-meta { display: block; color: var(--product-muted); font-size: 13px; line-height: 1.4; min-height: 1.4em; width: 100%; }
/* ---- The free catalog (Plex's own AVOD library): visually distinct from
   the viewer's own server everywhere the two could be confused -- a sidebar
   heading, a small "Free" badge on a card, a source label on a search row,
   and a group heading on Home. Never a different card/grid/hero shape --
   only these markers. See docs/plex-avod-discovery-2026-09-17.md. */
.plex-sidebar-heading { margin: 14px 0 2px; padding: 0 12px; color: var(--product-muted); font-size: 12px; font-weight: 700;
                         text-transform: uppercase; letter-spacing: .06em; }
.plex-sidebar-heading[hidden] { display: none; }
.plex-home-group-title { margin: 0 0 -12px; font-size: 15px; font-weight: 700; color: var(--product-muted); text-transform: uppercase; letter-spacing: .04em; }
.plex-item-badge { position: absolute; top: 6px; left: 6px; z-index: 1; padding: 2px 7px; border-radius: 999px;
                    background: oklch(21% .006 270 / .85); color: var(--product-text); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
.plex-source-badge { display: inline-flex; align-items: center; margin-left: 10px; padding: 2px 8px; border-radius: 999px;
                      background: var(--product-hover); color: var(--product-muted); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; vertical-align: middle; }
.plex-search-result-source { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--product-muted); font-size: 12px; font-weight: 650; }

/* ---- Placeholders: same boxes as the content they stand in for. */
.plex-skeleton-line { display: block; border-radius: 6px; background: var(--product-surface); color: transparent; }
.plex-item-skeleton { pointer-events: none; }
.plex-item-skeleton .plex-item-thumb { background: var(--product-surface); }
.plex-item-skeleton .plex-item-title { height: 1.35em; margin-top: 12px; margin-bottom: 4px; width: 78%; }
.plex-item-skeleton .plex-item-meta { height: 1em; min-height: 0; width: 42%; }
.plex-nav-skeleton .plex-skeleton-line { height: 14px; width: 62%; }
.plex-hero-skeleton .plex-hero-info .plex-skeleton-line { height: 14px; margin: 6px 0 0; }
.plex-hero-skeleton .plex-hero-title:empty { min-height: 1.2em; }
.plex-cast { min-height: 118px; }
.plex-cast:empty { visibility: hidden; }
/* The server row and the library heading row keep their loaded height
   before their content arrives. */
.plex-sidebar-server { box-sizing: border-box; }
.plex-browse-head { min-height: 40px; }
.plex-item-airdate { color: var(--product-muted); font-size: 12px; }
.plex-item-summary { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; color: var(--product-muted); font-size: 13px; line-height: 1.4; }
/* Bottom space on a title page comes from this one padding. It used to stack
   with the card's own bottom margin and the app's 90px bottom padding --
   about 176px of nothing under the card, most of a screen of scroll past the
   last thing on the page. */
.plex-detail { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; padding: 24px 34px 34px; }
.plex-app:has(.plex-detail:not([hidden])) { padding-bottom: 0; }
/* The hero fills the content column rather than shrinking to its own text:
   the page is a column flex box aligned to its start, which otherwise left a
   ragged gap down the right of the card. `align-self` rather than a width,
   so the card's own 26px padding stays inside the column. */
.plex-detail > .plex-hero { align-self: stretch; margin-bottom: 0; }
.plex-detail-title { font-size: 24px; font-weight: 700; margin: 6px 0 0; }
.plex-detail .plex-hero-actions { margin-top: 4px; width: 200px; }
/* Breadcrumb wayfinding on the show/season pages, replacing the old Back
   button: a link to the parent (a library, or a show) plus the current
   page as plain text -- Plex Web's own convention. */
.plex-crumb { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 15px; }
.plex-crumb:empty { display: none; }
.plex-crumb-link { border: 0; padding: 0; background: transparent; color: var(--product-muted); font-weight: 650; font: inherit; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.plex-crumb-link:hover { color: var(--product-text); }
.plex-crumb-sep { color: var(--product-muted); }
.plex-crumb-current { color: var(--product-text); font-weight: 650; }
/* ---- The metadata hero (movie/episode detail, and a show/season's own
   container view): blurred/darkened backdrop behind a poster and the full
   set of descriptive fields, plus a cast row with headshots. Shared by
   watchHero and containerHero in plex.js -- same markup, same rules. */
.plex-hero[hidden] { display: none; }
.plex-hero { position: relative; display: grid; grid-template-columns: 200px 1fr; grid-template-rows: auto auto; align-items: start;
             column-gap: 26px; row-gap: 12px; margin-bottom: 26px; padding: 26px; border-radius: 16px;
             background: var(--product-surface); overflow: hidden; isolation: isolate; }
.plex-hero-backdrop { position: absolute; inset: 0; z-index: -1; background: var(--product-bg); }
.plex-hero-backdrop img { width: 100%; height: 100%; object-fit: cover; filter: blur(18px) brightness(.4); transform: scale(1.1); }
.plex-hero-backdrop::after { content: ''; position: absolute; inset: 0; background: linear-gradient(to top, var(--product-surface), rgb(0 0 0 / .25)); }
.plex-hero-poster { grid-column: 1; grid-row: 1; width: 200px; aspect-ratio: 2 / 3; object-fit: cover; border-radius: 10px; box-shadow: 0 12px 32px rgb(0 0 0 / .55); background: var(--product-bg); }
.plex-hero-poster[hidden] { display: none; }
.plex-hero-poster-blank { display: block; background: var(--product-bg); box-shadow: none; }
/* The title page's own Play/Resume (+ optional Start from beginning) button
   group, reparented directly under the poster once the hero loads (see
   plex.js's renderHero()/loadDetailMetadata()): pinned to the poster's own
   grid column so it always lands on the row right below it, full poster
   width -- never beside the poster in the text column. Buttons stack full
   width and keep the base `.plex-primary`/`.plex-secondary` 52px min-height
   (the car's minimum comfortable touch target) rather than a shorter override. */
.plex-hero > .plex-hero-actions { grid-column: 1; grid-row: 2; width: 200px; margin: 0; justify-self: start; align-self: start; }
.plex-hero-actions { display: flex; flex-direction: column; gap: 10px; }
/* One line each, whatever the resume time reads ("Resume from 1:05:12"). */
.plex-hero-actions .plex-primary, .plex-hero-actions .plex-secondary { width: 100%; margin: 0; padding: 0 10px; font-size: 15px; white-space: nowrap; }
.plex-hero-actions .plex-secondary[hidden] { display: none; }
.plex-hero-info { grid-column: 2; grid-row: 1 / span 2; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.plex-hero-title { font-size: 30px; font-weight: 750; letter-spacing: -.4px; margin: 0; }
.plex-hero-tagline { font-style: italic; color: var(--product-muted); margin: 0; }
.plex-hero-facts, .plex-hero-ratings, .plex-hero-genres { color: var(--product-muted); font-size: 14px; margin: 0; }
.plex-hero-summary { max-width: 74ch; line-height: 1.55; margin: 10px 0 0; }
.plex-cast { grid-column: 1 / -1; display: flex; gap: 18px; overflow-x: auto; padding-top: 4px; scrollbar-width: none; }
.plex-cast::-webkit-scrollbar { display: none; }
.plex-cast-card { flex: 0 0 96px; display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px; }
.plex-cast-card img { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; background: var(--product-bg); }
.plex-cast-name { font-size: 12px; font-weight: 650; line-height: 1.3; }
.plex-cast-role { font-size: 11px; color: var(--product-muted); line-height: 1.3; }
/* Hosts the reused-as-is player.js '.stage' element (see plex.js's
   borrowStage()), sized exactly like the YouTube watch page's own
   '.product-player' (see product.css above): an aspect-ratio surface
   clamped to fit the viewport height (minus the header and the title/meta
   block below it -- see '--plex-watch-title-height', fed by plex.js's own
   ResizeObserver, the same technique as product.js's fitPlayer()), with an
   overlay notice/spinner shown while loading. */
.plex-player { position: relative; min-width: 0; margin: 0; width: 100%;
                height: calc(100vh - var(--product-header-height)); height: calc(100dvh - var(--product-header-height));
                /* The watch page gives the player the whole content column,
                   header to the bottom of the screen, whatever its shape; the
                   whole picture always shows inside it (object-fit: contain
                   on the canvas), with bars at the sides or top/bottom as the
                   screen's own shape dictates. No rounded corners, no gutter,
                   nothing to scroll. */
                border-radius: 0; overflow: hidden; background: #000; }
.plex-player-notice { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 24px; text-align: center; border-radius: inherit; background: #000; }
.plex-player-notice p { margin: 0; }
.plex-player[data-loading=true] .plex-player-notice { background: transparent; pointer-events: none; }
.plex-player[data-loading=true] #screen, .plex-player[data-loading=true] .center-btn { visibility: hidden; }
.plex-player[data-loading=true] .controls { opacity: 1; visibility: visible; pointer-events: none; }
.plex-player[data-loading=true] #fs { pointer-events: auto; }
.plex-player .stage { width: 100%; height: 100%; border: 0; border-radius: 0; }
.plex-player .stage canvas#screen { width: 100%; height: 100%; object-fit: contain; object-position: center; }
.plex-player .screen-wrap { height: 100%; aspect-ratio: auto; }
.plex-player .meta { display: none; }
.plex-player-message { color: var(--product-muted); margin: 0; font-size: 16px; }
/* ---- The watch page ('/plex/watch/<ratingKey>'): mirrors the YouTube
   watch page's own layout (see '.product-watch'/'.product-viewing' above)
   minus the Up next column -- a single, centered column with the video on
   top and the title/meta directly beneath it, same spot as YouTube's own.
   A sibling of .plex-shell rather than a child of it, so it can hide the
   sidebar entirely while the real shared #site-header above stays put.
   There is no close/X control -- see plex.js's watchPage comment; Escape is
   the one keyboard shortcut back (plex.js's onKeyDown()). */
.plex-watch[hidden] { display: none; }
.plex-watch { width: 100%; max-width: none; margin: 0; padding: 0; display: flex; align-items: flex-start; background: #000; }
/* While watching, the shared header and the sidebar stay for navigation;
   the player is the full column width below the header. */
.plex-watch-stage { width: 100%; max-width: none; height: 100%; margin: 0; }
.plex-watch-info { position: absolute; top: 0; left: 0; right: 0; z-index: 7; padding: 18px 24px 40px; pointer-events: none;
                   background: linear-gradient(to bottom, rgb(0 0 0 / .75), rgb(0 0 0 / 0)); border-radius: 0;
                   opacity: 0; visibility: hidden; transition: opacity .2s, visibility .2s; }
.plex-watch-stage:has(.controls.show) .plex-watch-info, .plex-watch-stage:has([data-loading=true]) .plex-watch-info { opacity: 1; visibility: visible; }
.plex-watch-title { font-size: 22px; line-height: 1.3; margin: 0 0 2px; font-weight: 650; overflow-wrap: anywhere; text-shadow: 0 1px 2px rgb(0 0 0 / .6); }
.plex-watch-meta { color: rgb(255 255 255 / .8); font-size: 15px; margin: 0; text-shadow: 0 1px 2px rgb(0 0 0 / .6); }
.plex-watch-stage { position: relative; width: 100%; }
.plex-watch-meta[hidden] { display: none; }
@media (min-width: 1700px) { .plex-items, .plex-home-scroller { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); } }
/* TV/narrow desktop: the sidebar collapses to an icon-only rail (labels
   hidden, kept for screen readers) so it never crowds out the grid. */
@media (max-width: 1100px) {
  .plex-sidebar { flex-basis: 72px; width: 72px; padding: 18px 8px; }
  .plex-sidebar-head .plex-brand { display: none; }
  .plex-sidebar-server, .plex-nav-item span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .plex-nav-item { justify-content: center; padding: 0; }
  .plex-hero { grid-template-columns: 150px 1fr; }
  .plex-hero-poster { width: 150px; }
}
@media (max-width: 640px) {
  .plex-topbar-row { padding: 12px 18px; }
  /* Phones: the sidebar stacks above the content as a horizontal bar
     (server row, then a scrollable Home/library strip) instead of a
     left-hand column. */
  .plex-shell { flex-direction: column; }
  .plex-sidebar { position: static; flex-basis: auto; width: 100%; height: auto; flex-direction: row; flex-wrap: wrap;
                  align-items: center; border-right: 0; border-bottom: 1px solid var(--product-line); padding: 10px 16px; overflow: visible; }
  .plex-sidebar-server { flex: 1 1 100%; border-bottom: 0; margin-bottom: 8px; padding: 0; }
  .plex-sidebar-nav { flex-direction: row; overflow-x: auto; scrollbar-width: none; flex: 1 1 auto; min-width: 0; }
  .plex-sidebar-nav::-webkit-scrollbar { display: none; }
  .plex-sidebar-sections { flex-direction: row; margin: 0 0 0 8px; padding: 0 0 0 8px; border-top: 0; border-left: 1px solid var(--product-line); }
  .plex-nav-item span { position: static; width: auto; height: auto; overflow: visible; clip: auto; }
  .plex-nav-item { width: auto; flex-shrink: 0; }
  .plex-sidebar-bottom { margin-top: 0; margin-left: 8px; padding-top: 0; border-top: 0; border-left: 1px solid var(--product-line); }
  .plex-home { padding: 18px 16px 32px; }
  .plex-connect { padding: 40px 18px 30px; }
  .plex-library { padding: 4px 18px 32px; }
  .plex-container { padding: 4px 18px 32px; }
  .plex-detail { padding: 20px 18px 40px; }
  .plex-settings { padding: 20px 18px 40px; }
  .plex-watch { padding: 16px 18px 32px; }
  .plex-items { gap: 22px 14px; }
  .plex-hero { grid-template-columns: 1fr; padding: 18px; }
  .plex-hero-poster { width: 120px; }
  .plex-browse-controls { width: 100%; margin-left: 0; }
}

/* Platform Account page ('/account'): identity, password reset and sign-out.
   Optional YouTube connection management stays a separate link to /yt/account. */
.account-app { max-width: 640px; margin: 0 auto; padding: 64px 34px 90px; }
.account-heading h1 { font-size: 36px; font-weight: 700; margin: 0 0 32px; }
.account-card { margin-bottom: 24px; padding: 24px 26px; border: 1px solid var(--product-line); border-radius: 14px; background: var(--product-surface); }
.account-card h2 { font-size: 20px; margin: 0 0 12px; }
.account-card p { color: var(--product-muted); margin: 0 0 6px; font-size: 16px; max-width: 60ch; }
.account-identity-name { color: var(--product-text) !important; font-size: 20px !important; font-weight: 650; }
.account-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; }
.account-actions button, .account-link { display: inline-flex; align-items: center; justify-content: center; min-height: 52px; padding: 0 22px;
                                          border-radius: 10px; font-weight: 650; text-decoration: none; }
button[data-account=reset-submit], button[data-account=sign-out] { border: 0; background: var(--product-accent); color: oklch(98% .002 270); }
button[data-account=reset-submit]:disabled { opacity: .5; cursor: default; }
button[data-account=reset-cancel], .account-link { border: 1px solid var(--product-line); background: transparent; color: inherit; }
.account-link { margin-top: 4px; }
button[data-account=reset-submit]:hover:not(:disabled), button[data-account=sign-out]:hover { filter: brightness(1.12); }
button[data-account=reset-cancel]:hover, .account-link:hover { background: var(--product-hover); }
@media (min-width: 1700px) { .product-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 1200px) {
  #site-header { padding: 12px 20px; gap: 16px; }
  #site-controls { gap: 16px; }
  #auth-identity { display: none; }
  .product-content { padding: 20px 24px 40px; }
  .product-grid { gap: 28px 17px; }
}
@media (max-width: 1400px) { .product-watch { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 1280px) {
  .product-app { grid-template-columns: minmax(0, 1fr); }
  .product-grid, .product-next-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .product-nav { position: fixed; z-index: 19; top: var(--product-header-height); bottom: 0; left: 0; width: min(280px, 85vw); height: calc(100vh - var(--product-header-height)); height: calc(100dvh - var(--product-header-height)); overflow-y: auto; background: var(--product-bg); padding: 16px 12px; border-right: 1px solid var(--product-line); }
}
@media (max-width: 850px) {
  .product-collections { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .product-content { padding: 18px; }
}
@media (max-width: 600px) {
  /* Only /yt actually wraps a search row under the header; the launcher and
     Account page never show search, so they keep the compact header height
     (this also protects the /yt-only player/nav height calc formulas below,
     which still see the correct taller value whenever they are relevant). */
  body[data-route="yt"], body[data-route="plex"] { --product-header-height: 148px; }
  #site-header { flex-wrap: wrap; padding: 12px; gap: 8px; }
  #site-controls { gap: 8px; }
  #site-header #auth-logout { min-height: 56px; padding: 7px 12px; }
  #product-search-mount { flex-basis: 100%; order: 2; }
  #product-search-mount:has(.product-search[hidden]) { display: none; }
  #product-search-mount:has(.plex-search[hidden]) { display: none; }
  .product-search { height: 56px; }
  .product-app { display: block; }
  .product-content { padding: 24px 16px 40px; }
  .product-heading { margin-bottom: 19px; }
  .product-heading h1 { font-size: 30px; }
  .product-channel-heading { align-items: flex-start; gap: 16px; }
  .product-channel-photo .product-avatar { width: 72px; height: 72px; font-size: 30px; }
  .product-grid { grid-template-columns: 1fr; gap: 28px; }
  .product-collections { grid-template-columns: 1fr; }
  .product-next-list { display: flex; }
  #site-header .logo { font-size: 22px; }
  #site-header .product-mark svg { width: 30px; }
  #site-header #auth-logout { font-size: 16px; }
  .product-player .controls { gap: 6.25px; padding: 10px; }
  .product-player .controls .t { font-size: 18.75px; min-width: 55px; }
  .product-player .controls button { width: 60px; height: 60px; flex-basis: 60px; }
}
@media (prefers-reduced-motion: reduce) { .product-thumbnail img, .product-autoplay input::before { transition: none; } }

#product-search-mount { position: relative; }
.product-suggestions { position: absolute; top: calc(100% + 8px); left: 50%; transform: translateX(-50%); z-index: 30; width: min(100%, 630px); max-height: min(520px, 65vh); overflow-y: auto; padding: 8px; border: 1px solid var(--product-line); border-radius: 16px; background: var(--product-surface); box-shadow: 0 8px 28px rgb(0 0 0 / .35); }
.product-suggestion { display: flex; align-items: center; gap: 16px; width: 100%; min-height: 56px; padding: 10px 14px; border: 0; border-radius: 9px; background: transparent; color: var(--product-text); text-align: left; cursor: pointer; }
.product-suggestion:hover, .product-suggestion[aria-selected=true] { background: var(--product-hover); }
.product-suggestion svg { width: 24px; height: 24px; flex-shrink: 0; fill: none; stroke: currentColor; stroke-width: 1.7; }
.product-suggestion span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- Plex full-height sidebar layout: the sidebar itself is already
   position:fixed, full viewport height (see .plex-sidebar above); these
   rules offset the shared header's own content and the Plex content column
   to the right by the sidebar's width, so #site-header only ever appears to
   span the column to the right of the sidebar -- the two-column look of
   Plex Web's own layout -- without restructuring the shared shell's DOM.
   Scoped to :has(.plex-sidebar:not([hidden])) so the pre-connect flow and
   the full-bleed watch page, both of which hide the sidebar, are never
   offset; placed at the very end of this file so it wins over the earlier,
   route-agnostic #site-header responsive padding rules at the same
   breakpoints. Below 641px the sidebar itself goes back to a normal,
   non-fixed horizontal bar (see the existing max-width:640px rule above),
   so no offset applies there either.
   `.plex-shell`'s own base rule sets width:100% (of the body, since the
   fixed-position sidebar takes no space in normal flow); adding
   margin-left here without shrinking that width pushes the shell's right
   edge past the viewport by exactly the sidebar's own width, forcing a
   page-level horizontal scrollbar. `width: calc(100% - <sidebar width>)`
   keeps the shell's right edge flush with the viewport instead. */
@media (min-width: 641px) {
  body[data-route="plex"]:has(.plex-sidebar:not([hidden])) #site-header { padding-left: calc(240px + 30px); }
  body[data-route="plex"]:has(.plex-sidebar:not([hidden])) .plex-shell { width: calc(100% - 240px); margin-left: 240px; }
  body[data-route="plex"]:has(.plex-sidebar:not([hidden])) .plex-watch { width: calc(100% - 240px); margin-left: 240px; max-width: none; }
}
@media (min-width: 641px) and (max-width: 1100px) {
  /* The sidebar itself narrows to a 72px icon-only rail in this range
     (see the existing max-width:1100px rule above) -- the offset follows. */
  body[data-route="plex"]:has(.plex-sidebar:not([hidden])) #site-header { padding-left: calc(72px + 20px); }
  body[data-route="plex"]:has(.plex-sidebar:not([hidden])) .plex-shell { width: calc(100% - 72px); margin-left: 72px; }
  body[data-route="plex"]:has(.plex-sidebar:not([hidden])) .plex-watch { width: calc(100% - 72px); margin-left: 72px; max-width: none; }
}
/* Plex Web-style search: left-aligned at the content gutter (not centered,
   unlike /yt's), immediately to the right of the sidebar. */
body[data-route="plex"] #product-search-mount { justify-content: flex-start; }

/* ---- IPTV: a viewer's own playlist as a TV guide (public/iptv.js) ------ */
/* The screen is the bar plus the guide, which scrolls within itself: no
   page scroll past it. While a channel plays the player takes the viewport
   below the bar and the guide follows beneath it, so the page then scrolls
   exactly one guide further and no more. */
.iptv-app { width: 100%; margin: 0; padding: 0; }
body[data-route="iptv"] { overflow-x: hidden; }
.iptv-app[hidden] { display: none; }
.iptv-app button { font: inherit; cursor: pointer; color: inherit; }
.iptv-app button:disabled { opacity: .55; cursor: default; }
.iptv-app button:focus-visible, .iptv-app a:focus-visible { outline: 2px solid var(--product-text); outline-offset: 3px; }
.iptv-app input:focus-visible, .iptv-app select:focus-visible { outline: none; border-color: var(--product-muted); }
/* The shared site header carries nothing on this screen (no logo, no
   account controls), so it is not shown; the guide's own bar is the top. */
body[data-route="iptv"] #site-header { display: none; }
body[data-route="iptv"] { --product-header-height: 0px; }
.iptv-topbar { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; gap: 16px; padding: 14px 30px; background: var(--product-bg); border-bottom: 1px solid var(--product-line); }
.iptv-topbar .site-icon-link { width: 44px; height: 44px; flex-shrink: 0; }
.iptv-topbar .site-icon-link svg { width: 22px; height: 22px; }
.iptv-heading { margin: 0; font-size: 24px; font-weight: 750; letter-spacing: -.4px; white-space: nowrap; }
.iptv-search { flex: 1 1 240px; max-width: 420px; }
.iptv-search input, .iptv-group, .iptv-connect-form input { width: 100%; min-height: 48px; padding: 0 16px; border: 1px solid var(--product-line); border-radius: 12px; background: var(--product-surface); color: var(--product-text); font: inherit; }
.iptv-group { width: auto; max-width: 320px; padding-right: 36px; }
.iptv-search[hidden], .iptv-group[hidden], .iptv-connect[hidden], .iptv-guide[hidden], .iptv-watch[hidden], .iptv-existing[hidden], .iptv-notice[hidden], .iptv-more[hidden], .iptv-guide-empty[hidden], .iptv-now[hidden], .iptv-player-message[hidden], .iptv-spinner[hidden], button[hidden] { display: none; }
.iptv-notice { margin: 12px 30px; color: var(--product-muted); }
.iptv-primary, .iptv-secondary { min-height: 48px; padding: 0 22px; border: 0; border-radius: 12px; font-weight: 650; }
.iptv-primary { background: var(--product-accent); color: #fff; }
.iptv-secondary { background: var(--product-surface); color: var(--product-text); }
.iptv-secondary:hover, .iptv-primary:hover { filter: brightness(1.15); }
.iptv-danger { color: oklch(78% .17 27); }
.iptv-connect { max-width: 760px; margin: 40px auto; padding: 0 30px; }
.iptv-connect h2 { margin: 0 0 8px; font-size: 28px; }
.iptv-connect-intro { margin: 0 0 24px; color: var(--product-muted); line-height: 1.45; }
.iptv-connect-form { display: flex; flex-direction: column; gap: 16px; }
.iptv-connect-form label { display: flex; flex-direction: column; gap: 8px; font-weight: 650; }
.iptv-connect-status { min-height: 1.4em; margin: 0; color: var(--product-muted); }
.iptv-existing { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--product-line); }
.iptv-existing-text { flex: 1 1 100%; margin: 0 0 6px; color: var(--product-muted); line-height: 1.45; }
/* The player above the guide: the whole viewport below the bar while a
   channel plays, the guide scrolling on beneath it. */
.iptv-watch { position: relative; z-index: 5; width: 100%; height: calc(100vh - 77px); background: #000; }
.iptv-player { position: relative; width: 100%; height: 100%; margin: 0 auto; background: #000; }
.iptv-player .stage { position: absolute; inset: 0; width: 100%; height: 100%; max-width: none; margin: 0; border: 0; border-radius: 0; }
.iptv-player .screen-wrap { height: 100%; aspect-ratio: auto; }
.iptv-player:has(.stage) .iptv-spinner { display: none; }
.iptv-spinner { position: absolute; inset: 0; margin: auto; width: 48px; height: 48px; border: 4px solid oklch(100% 0 0 / .25); border-top-color: #fff; border-radius: 50%; animation: iptv-spin .9s linear infinite; }
@keyframes iptv-spin { to { transform: rotate(360deg); } }
.iptv-player-message { position: absolute; left: 0; right: 0; top: 50%; margin: 0; padding: 0 24px; text-align: center; font-size: 20px; text-shadow: 0 1px 2px rgb(0 0 0 / .6); z-index: 8; }
.iptv-player [data-iptv="player-retry"] { position: absolute; left: 50%; top: calc(50% + 40px); transform: translateX(-50%); z-index: 8; }
.iptv-watch-info { position: absolute; top: 0; left: 0; right: 0; z-index: 7; padding: 18px 70px 40px 24px; pointer-events: none; background: linear-gradient(rgb(0 0 0 / .7), rgb(0 0 0 / 0)); opacity: 0; visibility: hidden; transition: opacity .2s; }
.iptv-watch:has(.controls.show) .iptv-watch-info, .iptv-watch:has([data-loading=true]) .iptv-watch-info { opacity: 1; visibility: visible; }
.iptv-watch-title { margin: 0 0 2px; font-size: 22px; font-weight: 650; text-shadow: 0 1px 2px rgb(0 0 0 / .6); }
.iptv-watch-programme { margin: 0; color: oklch(88% .01 270); text-shadow: 0 1px 2px rgb(0 0 0 / .6); }
.iptv-close { position: absolute; top: 12px; right: 14px; z-index: 9; background: rgb(0 0 0 / .45); }
/* The guide grid: a fixed channel column, a horizontally scrolling timeline. */
.iptv-guide { position: relative; z-index: 1; }
.iptv-guide-scroll { overflow-y: auto; overflow-x: hidden; height: calc(100vh - 77px); }
.iptv-guide-inner { position: relative; }
.iptv-ruler { position: sticky; top: 0; z-index: 4; display: flex; height: 40px; background: var(--product-bg); border-bottom: 1px solid var(--product-line); }
.iptv-ruler-corner { position: sticky; left: 0; z-index: 5; flex: 0 0 220px; width: 220px; background: var(--product-bg); border-right: 1px solid var(--product-line); }
.iptv-ruler-times { position: relative; height: 100%; }
.iptv-ruler-mark { position: absolute; top: 0; height: 100%; padding: 10px 0 0 8px; border-left: 1px solid var(--product-line); color: var(--product-muted); font-size: 15px; font-weight: 650; white-space: nowrap; }
.iptv-ruler-times, .iptv-lane { overflow: hidden; }
.iptv-row { display: flex; border-bottom: 1px solid var(--product-line); }
.iptv-row-playing .iptv-channel { background: oklch(30% .05 27); }
.iptv-channel { position: sticky; left: 0; z-index: 3; display: flex; align-items: center; gap: 12px; flex: 0 0 220px; width: 220px; padding: 0 12px; border: 0; border-right: 1px solid var(--product-line); background: var(--product-surface); text-align: left; }
.iptv-channel:hover { background: var(--product-hover); }
.iptv-channel-number { flex: 0 0 34px; color: var(--product-muted); font-size: 15px; font-weight: 650; }
.iptv-channel-logo { display: flex; align-items: center; justify-content: center; flex: 0 0 48px; width: 48px; height: 40px; border-radius: 8px; background: oklch(100% 0 0 / .06); color: var(--product-muted); font-size: 14px; font-weight: 700; overflow: hidden; }
.iptv-channel-logo img { max-width: 100%; max-height: 100%; object-fit: contain; }
.iptv-channel-name { overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; font-size: 16px; font-weight: 650; line-height: 1.25; }
.iptv-lane { position: relative; height: 100%; }
.iptv-programme { position: absolute; top: 6px; bottom: 6px; display: flex; flex-direction: column; justify-content: center; gap: 2px; padding: 0 12px; border: 0; border-radius: 10px; background: var(--product-surface); color: var(--product-text); text-align: left; overflow: hidden; }
.iptv-programme:hover { background: var(--product-hover); }
.iptv-programme-now { background: oklch(28% .02 270); box-shadow: inset 0 0 0 2px var(--product-accent); }
.iptv-programme-past { color: var(--product-muted); }
.iptv-programme-empty { justify-content: flex-start; color: var(--product-muted); font-size: 15px; }
.iptv-programme-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 16px; font-weight: 650; }
.iptv-programme-time { color: var(--product-muted); font-size: 14px; }
.iptv-now { position: absolute; top: 0; bottom: 0; z-index: 4; width: 2px; background: var(--product-accent); pointer-events: none; }
.iptv-now::before { content: ''; position: absolute; top: 34px; left: -5px; width: 12px; height: 12px; border-radius: 50%; background: var(--product-accent); }
.iptv-more { height: 1px; }
.iptv-guide-empty { margin: 30px; color: var(--product-muted); }

/* Account: today's viewing allowance (public/launcher.js's account page). */
.account-viewing-bar { height: 10px; margin-top: 10px; border-radius: 5px; background: oklch(100% 0 0 / .08); overflow: hidden; }
.account-viewing-fill { height: 100%; width: 0; border-radius: 5px; background: var(--product-accent); transition: width .3s; }
