h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ---- IkaFin TV-friendly UI ---- */

/* Fonts: one @font-face per weight, using the static files.
   ================================================================================================
   These were previously two faces declared `font-weight: 400 700`. That weight-RANGE descriptor is
   CSS Fonts 4 and needs Chrome 62; on the target runtime (Chromium 53) it is an invalid descriptor,
   so those faces did not register as providing weight 600 at all. Six rules in this file ask for
   600, so every heading and label fell back to whatever single instance did load - which is exactly
   the reported symptom: text that looks light or thin no matter how large it gets. The range syntax
   also implies variable fonts, which are Chrome 62 as well, so the file could not have supplied
   multiple weights on this engine even if the descriptor had parsed.

   The static per-weight files were already sitting in the folder unused. One face per weight is
   understood by every engine, so 200/400/600 now all resolve to a real font file.

   The extended-Latin face has no static equivalent available, so it keeps the variable file. It only
   covers accented and symbol ranges the basic-Latin faces do not, and on an old engine a miss there
   degrades to the fallback font for those few glyphs rather than affecting body text.
   ================================================================================================ */

@font-face {
    font-family: 'Montserrat';
    font-style: normal;
    font-weight: 200;
    font-display: swap;
    src: url('fonts/montserrat-v31-latin-200.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Montserrat';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/montserrat-v31-latin-regular.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Montserrat';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/montserrat-v31-latin-600.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Extended Latin only - no static file exists for these ranges. */
@font-face {
    font-family: 'Montserrat';
    font-style: normal;
    font-weight: 400 700; /* ok-on-old-engines: no static ext file; affects only accented glyphs */
    font-display: swap;
    src: url('fonts/Montserrat-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
    --brand-color: #B7282E;
    --dark-color: #160000;
    --bright-color: #FFE5DE;

    /* Dark-maroon surface tones derived from --dark-color, used instead of neutral greys
       so cards/panels/side-menu read as part of the brand rather than plain black. */
    --surface-1: #1c0505;
    /* side menu, page chrome */
    --surface-2: #240808;
    /* cards, rows, panels */
    --surface-3: #2e0d0d;
    /* image placeholders, hover states */
    --surface-border: rgba(255, 229, 222, 0.08);
    --text-muted: #c9a8a3;
}

body {
    margin: 0;
    font-family: 'Montserrat', sans-serif;
    background-image: linear-gradient(to right top, #b7282e, #8b2027, #61191e, #391114, #160000);
    background-attachment: fixed;
    background-repeat: no-repeat;
    background-size: cover;
    color: #eee;
    min-height: 100vh;
}

/* TV mode never scrolls the window - focused rows are brought into view by collapsing
   already-passed rows instead (see FocusNavigationService/remoteNavigation.js), so the
   scrollbar would otherwise sit there unused. */
body.tv-mode {
    overflow: hidden;
}

h1, h2, h3, h4, h5, h6 {
    color: var(--bright-color);
}

.connect-page {
    max-width: 420px;
    margin: 3rem auto;
    display: flex;
    flex-direction: column;
}

.connect-logo {
    display: block;
    height: 72px;
    width: auto;
    margin: 0 auto 0.5rem;
    /* Logo artwork is solid black; invert so it reads as a light mark against the
       dark gradient page background. */
    filter: invert(1);
}

.poster-grid {
    /* Was display:grid with repeat(auto-fill, minmax(180px, 1fr)) + gap. Grid is Chrome 57 and
       gap 84, so this is flex-wrap with fixed-width cards and margins instead. Cards no longer
       stretch to fill the row; they keep the same 180px they already have in .poster-row. */
    display: flex;
    flex-wrap: wrap;
}

.home-section {
    display: flex;
    flex-direction: column;
    margin-bottom: 2rem;
}

.home-section-title {
    margin: 0;
    font-size: 1.4rem;
    font-weight: 200;
}

.poster-row {
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x proximity;
    padding: 0.25rem 0.25rem 1rem;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

    .poster-row::-webkit-scrollbar {
        display: none;
    }

    .poster-row .poster-card {
        flex: 0 0 auto;
        width: 180px;
        scroll-snap-align: start;
        margin-top: 9px;
        align-content: center;
    }

.library-sentinel {
    height: 1px;
}

.poster-card {
    color: inherit;
    text-decoration: none;
    display: block;
}    .poster-card:focus {
        outline: 0px none var(--brand-color);
        outline-offset: -3px;
        transform: scale(1.03);
/*        border: solid var(--brand-color);
        border-radius: 4px;*/
width:200px;
    }

    /* Split from the rule above: an unsupported pseudo-class in a comma list
       invalidates the whole list, which would drop the :focus styling too. */
    .poster-card:focus-visible {
        outline: 0px none var(--brand-color);
        outline-offset: -3px;
        transform: scale(1.03);
/*        border: solid var(--brand-color);
        border-radius: 4px;*/
width:200px;
    }
        /* No border here: the focus ring is the ::after overlay further down. Keeping this one as
           well drew two rings on Home - Library and Series happened to cancel it, which is why it
           only showed there. The width still grows on focus; that is the Home grow effect. */
        .poster-card:focus .poster-card-image-wrapper {
            width: 200px;
        }

        .poster-card:focus-visible .poster-card-image-wrapper {
            width: 200px;
        }


/* Fills its wrapper, which owns the aspect ratio (see .poster-card-image-wrapper).
   A fixed height here was wrong: it does not track the card width, so the grow-on-focus rule
   (.poster-card:focus sets width: 200px) reshaped the picture instead of scaling it, and in a grid
   the changed height pushed neighbouring cards onto other rows. */
.poster-card-image {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: var(--surface-3);
    /* `display: flex` was removed here. It is meaningless on a replaced element like <img> in modern
       engines, but old Blink handles that combination inconsistently - and this rule is the one that
       misbehaved on the TV, where the poster rendered at the wrong shape. Nothing needs it: the
       centering it provided was for a placeholder variant this port does not render, and alt text
       still centres via text-align. */
    text-align: center;
}

/* Currently unused by any component - kept in step with .poster-card-image. */
.library-tile {
    height: 101px; /* 16:9 of the 180px card width; was aspect-ratio (Chrome 88). */
    font-size: 1.4rem;
    font-weight: 600;
    padding: 0.5rem;
}

.poster-card-title {
    padding: 0.4rem 0.1rem;
    font-size: 0.95rem;
    /* Long titles would otherwise wrap and stretch the whole grid row, leaving
       neighbouring cards misaligned - keep every card the same height. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}.poster-card:focus .poster-card-title {
    padding-top: 0.3rem;
}

/* Split from the rule above: an unsupported pseudo-class in a comma list
   invalidates the whole list, which would drop the :focus styling too. */
.poster-card:focus-visible .poster-card-title {
    padding-top: 0.3rem;
}


.poster-card-subtitle {
    padding: 0 0.1rem 0.4rem;
    font-size: 0.8rem;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Owns the card's aspect ratio.
   `aspect-ratio` is Chrome 88, so the height comes from percentage padding instead - which is
   resolved against the WIDTH, so the ratio holds at any card width. That is what makes
   grow-on-focus scale the poster rather than reshape it. This has to live on the wrapper (a plain
   div) rather than on the <img>: percentage padding on a replaced element does not size it. */
.poster-card-image-wrapper {
    position: relative;
    height: 0;
    padding-top: 150%; /* 2:3 portrait poster */
    border-radius: 4px;
    overflow: hidden;
}

/* Landscape 16:9 artwork (episode thumbnails). */
.poster-card-image-wrapper.thumb-wrapper {
    padding-top: 56.25%;
}

.poster-card-progress {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 4px;
    background: rgba(0, 0, 0, 0.5);
}

.poster-card-progress-bar {
    height: 100%;
    background: var(--brand-color);
}

.poster-card-skeleton {
    width: 180px;
    flex: 0 0 auto;
}

    .poster-card-skeleton .poster-card-image-wrapper {
        background: var(--surface-3);
    }

/* The shimmer stands in for the poster, so it fills the wrapper exactly as the <img> does. */
.poster-card-image-wrapper .skeleton-shimmer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.skeleton-shimmer {
    border-radius: 4px;
    background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
    background-size: 400% 100%;
    animation: skeleton-shimmer 1.4s ease infinite;
}

.skeleton-shimmer.skeleton-text {
    /* Outside the image wrapper, so it needs its own height rather than filling a parent. */
    position: static;
    height: 0.9rem;
    margin: 0.4rem 0.1rem;
    border-radius: 3px;
}

@keyframes skeleton-shimmer {
    0% {
        background-position: 100% 50%;
    }

    100% {
        background-position: 0 50%;
    }
}

/* Landscape variant of .poster-grid for 16/9 artwork (episode thumbnails). Wider columns
   because the cards are wider than they are tall. */
.thumb-grid {
    /* See .poster-grid: grid + gap replaced by flex-wrap + margins. */
    display: flex;
    flex-wrap: wrap;
}

/* The ratio now lives on .thumb-wrapper; the class is kept for any thumb-specific styling. */
.poster-card-image.thumb-image {
    object-position: center;
}

/* Landscape cards in a horizontally-scrolling .poster-row need more width than the 180px
   portrait default. Sized via flex-basis rather than width so the :focus width rules can't
   fight it. */
.poster-row.thumb-row .poster-card {
    flex: 0 0 320px;
    width: 320px;
}

.poster-row.thumb-row .poster-card-image-wrapper {
    border-radius: 8px;
}

.item-detail {
    display: flex;
}

@media (max-width: 720px) {
    .item-detail {
        flex-direction: column;
    }

    .item-detail-image {
        width: 100%;
        max-width: 320px;
    }
}

.item-detail-image {
    width: 320px;
    border-radius: 6px;
}

.player-page {
    display: flex;
    flex-direction: column;
}

/* Positioning context so the buffering spinner can sit centred over the video. */
.player-stage {
    position: relative;
    display: flex;
}

.player-video {
    width: 100%;
    max-height: 80vh;
    background: black;
}

.player-spinner {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 3rem;
    height: 3rem;
    margin: -1.5rem 0 0 -1.5rem;
    border: 4px solid rgba(255, 255, 255, 0.25);
    border-top-color: var(--brand-color);
    border-radius: 50%;
    animation: player-spin 0.9s linear infinite;
    /* Purely decorative - must never swallow clicks meant for the video's own controls. */
    pointer-events: none;
}

@keyframes player-spin {
    to {
        transform: rotate(360deg);
    }
}

.player-error {
    display: flex;
    align-items: center;
}

.player-controls {
    display: flex;
    flex-direction: column;
}.player-controls button:focus,
button:focus {
    outline: 3px solid var(--brand-color);
    outline-offset: 2px;
}

/* Split from the rule above: an unsupported pseudo-class in a comma list
   invalidates the whole list, which would drop the :focus styling too. */
.player-controls button:focus-visible,
button:focus-visible {
    outline: 3px solid var(--brand-color);
    outline-offset: 2px;
}


/* ---- Player transport controls ----
   Bootstrap is long gone from this project, so the .btn/.btn-secondary classes these buttons used
   to carry resolved to nothing and they rendered as raw browser chrome. These rules give them a
   real media-player identity, borrowing the app's established focus idiom from .side-menu-item:
   brand-colour fill plus a hairline inset outline, with hover and focus treated identically. */
.player-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 3.25rem;
    height: 3.25rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    color: var(--bright-color);
    cursor: pointer;
    transition: background-color 0.18s ease, transform 0.18s ease;
}/* Deliberately more specific than the generic `.player-controls button:focus` ring above (0,
2,
1),
which would otherwise win and stamp a 3px outline on top of the fill. */
/* box-shadow rather than outline: these buttons are border-radius: 50%, and an outline is drawn as
   a rectangle on the target engine (outlines only began following border-radius around Chrome 94),
   so the ring appeared square around a circular button. An inset box-shadow follows the radius and
   costs no layout either. */
.player-controls .player-btn:hover,
.player-controls .player-btn:focus,
.player-endcard .player-btn:hover,
.player-endcard .player-btn:focus {
    background: var(--brand-color);
    box-shadow: inset 0 0 0 2px #fff;
    transform: scale(1.08);
}

/* Split from the rule above: an unsupported pseudo-class in a comma list
   invalidates the whole list, which would drop the :focus styling too. */
.player-controls .player-btn:focus-visible,
.player-endcard .player-btn:focus-visible {
    background: var(--brand-color);
    box-shadow: inset 0 0 0 2px #fff;
    transform: scale(1.08);
}


/* The primary action reads as primary at rest, not only when focused. */
.player-btn-primary {
    width: 4.5rem;
    height: 4.5rem;
    background: var(--brand-color);
}

.player-btn:disabled {
    opacity: 0.35;
    cursor: default;
}

.player-controls .player-btn:disabled:hover {
    background: rgba(255, 255, 255, 0.08);
    outline: none;
    transform: none;
}

.player-transport {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Same vocabulary as .poster-card-progress so progress reads the same everywhere in the app. */
.player-progress {
    height: 6px;
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.5);
    overflow: hidden;
}

.player-progress-bar {
    height: 100%;
    background: var(--brand-color);
    transition: width 0.25s linear;
}

.player-osd {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
}

.player-osd-title {
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--bright-color);
    text-shadow: 0 2px 12px var(--dark-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.player-osd-time {
    flex: 0 0 auto;
    font-size: 0.9rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.player-exit {
    position: absolute;
    top: 1.5rem;
    left: 1.5rem;
    z-index: 2;
}

/* Undefined until now - `text-danger` is used here and by Connect/Login/Error, and resolved to
   nothing after Bootstrap was removed. Lighter than the app's other error reds so it stays
   readable against the player's black frame. */
.text-danger {
    color: #ff8a8a;
}

/* ---- TV mode: full-screen playback ----
   In TV mode the picture has to fill the panel. The Fullscreen API can't deliver that: on webOS
   NativeShell.enableFullscreen() doesn't touch the DOM (the TV browser is already full-screen), and
   what actually boxes the video in is IkaFin's own chrome - the app header, the sticky side menu and
   the 205px TopInfoSection. Rather than overriding each of those (they live in separate scoped
   component stylesheets), the player lifts itself out of the layout as a fixed overlay that simply
   covers them. That also needs no user gesture, so it works on autoplay. */
.player-page.player-fullscreen {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    /* Above .app-header (20) and .top-info-container (15); still below #blazor-error-ui (1000). */
    z-index: 30;
    background: #000;
}

.player-fullscreen .player-stage {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
}

.player-fullscreen .player-video {
    width: 100%;
    height: 100%;
    max-height: none;
    /* Fill the panel without distorting non-16:9 material. */
    object-fit: contain;
}

.player-fullscreen .player-controls {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    /* Generous bottom padding: the focused button grows by 8%, and TV panels overscan. */
    padding: 3rem 2rem 2.5rem;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.85), transparent);
    transition: opacity 0.25s ease;
}

/* Hidden with opacity rather than display/@if: the buttons stay in the DOM so their
   ElementReferences remain valid for the D-pad focus scope registered by Player.razor. */
.player-fullscreen .player-controls.controls-hidden {
    opacity: 0;
    pointer-events: none;
}

.player-fullscreen .player-error {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    flex-direction: column;
    padding: 1.5rem 2rem;
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.75);
    text-align: center;
}

/* The player surface owns D-pad focus while the control panel is hidden, so that Left/Right/OK
   reach the page as playback commands instead of moving focus between buttons. It must never show
   a focus ring - the user is looking at the picture, not at a control. */
.player-surface {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
}.player-surface:focus {
    outline: none;
}

/* Split from the rule above: an unsupported pseudo-class in a comma list
   invalidates the whole list, which would drop the :focus styling too. */
.player-surface:focus-visible {
    outline: none;
}


/* ---- Track menu (audio / subtitles) ---- */
.player-menu {
    position: absolute;
    right: 2rem;
    bottom: 9rem;
    z-index: 3;
    display: flex;
    flex-direction: column;
    min-width: 16rem;
    max-width: 28rem; /* min(28rem, 80vw) needs Chrome 79; narrow case in a media query. */
    max-height: 60vh;
    overflow-y: auto;
    padding: 0.5rem;
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.88);
    border: 1px solid var(--surface-border);
}

.player-menu-heading {
    padding: 0.4rem 0.75rem 0.6rem;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.player-menu-item {
    display: flex;
    align-items: center;
    padding: 0.6rem 0.75rem;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: var(--bright-color);
    font-family: inherit;
    font-size: 0.95rem;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.18s ease;
}.player-menu .player-menu-item:hover,
.player-menu .player-menu-item:focus {
    background: var(--brand-color);
    box-shadow: inset 0 0 0 1px var(--bright-color);
}

/* Split from the rule above: an unsupported pseudo-class in a comma list
   invalidates the whole list, which would drop the :focus styling too. */
.player-menu .player-menu-item:focus-visible {
    background: var(--brand-color);
    box-shadow: inset 0 0 0 1px var(--bright-color);
}


/* Separated from the track list above it: this row is a setting, not another track. */
.player-menu-item-toggle {
    margin-top: 0.4rem;
    padding-top: 0.7rem;
    border-top: 1px solid var(--surface-border);
    border-radius: 0 0 4px 4px;
    font-size: 0.85rem;
    color: var(--text-muted);
}.player-menu .player-menu-item-toggle:hover,
.player-menu .player-menu-item-toggle:focus {
    color: var(--bright-color);
}

/* Split from the rule above: an unsupported pseudo-class in a comma list
   invalidates the whole list, which would drop the :focus styling too. */
.player-menu .player-menu-item-toggle:focus-visible {
    color: var(--bright-color);
}


/* Fixed-width gutter so the labels line up whether or not a row is ticked. */
.player-menu-check {
    flex: 0 0 auto;
    width: 1.1rem;
    display: flex;
    align-items: center;
    color: var(--brand-color);
}

.player-menu-item:hover .player-menu-check,
.player-menu-item:focus .player-menu-check {
    color: var(--bright-color);
}

.player-menu-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---- End-of-episode card ---- */
.player-endcard {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 3;
    display: flex;
    align-items: center;
    max-width: 46rem; /* min(46rem, 90vw) needs Chrome 79; narrow case in a media query. */
    padding: 1.5rem;
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.82);
    border: 1px solid var(--surface-border);
}

.player-endcard-thumb {
    flex: 0 0 auto;
    width: 14rem;
    max-width: 40vw;
    border-radius: 4px;
    background: var(--surface-3);
}

.player-endcard-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.player-endcard-label {
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.player-endcard-title {
    font-size: 1.5rem;
    font-weight: 600;
    color: var(--bright-color);
}

.player-endcard-actions {
    display: flex;
    margin-top: 0.5rem;
}

/* Text buttons here rather than icons: these are decisions, not transport controls. */
.player-text-btn {
    padding: 0.6rem 1.2rem;
    border: 1px solid var(--surface-border);
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.08);
    color: var(--bright-color);
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.18s ease;
}.player-endcard .player-text-btn:hover,
.player-endcard .player-text-btn:focus,
.player-error .player-text-btn:hover,
.player-error .player-text-btn:focus {
    background: var(--brand-color);
    box-shadow: inset 0 0 0 1px var(--bright-color);
}

/* Split from the rule above: an unsupported pseudo-class in a comma list
   invalidates the whole list, which would drop the :focus styling too. */
.player-endcard .player-text-btn:focus-visible,
.player-error .player-text-btn:focus-visible {
    background: var(--brand-color);
    box-shadow: inset 0 0 0 1px var(--bright-color);
}



/* ============================================================================================
   Chromium 53 layout fallbacks
   ============================================================================================
   The target runtime (webOS 4.x WAM, measured as Chromium 53-55) has no flexbox `gap` - that is
   Chrome 84. Unsupported declarations are silently ignored rather than raising an error, so a
   layout written with `gap` looks correct in desktop Chrome and collapses on the TV with nothing
   in any log to explain it. Every gap in this file is therefore expressed as margins here.
   `> * + *` targets the second and later children, which reproduces gap exactly for a
   single-line flex container. Wrapping containers cannot use it - the first child of each new
   line has no preceding sibling - so those set a margin on every child instead.
   Enforced by scripts/check-css.mjs.
   ============================================================================================ */

/* --- visible focus indicator ---------------------------------------------------------------
   The global .poster-card:focus rule sets `outline: 0px none` and its border-based cue is
   commented out, so on Home the only feedback was the grow-on-focus. In the Library and Series
   grids that grow is deliberately cancelled (it would shift the neighbouring cards and move the
   spatial-navigation targets under the user), which left those grids with no focus indicator at
   all - unusable with a D-pad.

   An outline is the right cue here precisely because it is not part of layout: it cannot shift
   neighbours the way the old `border: solid` did. A negative offset draws it inside the artwork,
   so it stays within the card's box. :focus and :focus-visible are separate rules, as ever.        */
/* Drawn as an ::after overlay rather than an `outline` on the wrapper.
   The artwork is an absolutely positioned <img> filling that wrapper, so it paints on top of the
   wrapper's own outline - leaving the ring visible only in the few pixels where border-radius clips
   the image corners. A generated ::after is the wrapper's last child, so it paints above the image.
   `border-radius: inherit` keeps it hugging the artwork, including the 8px radius thumb variant.

   White, not --brand-color (#B7282E): the brand red sits on a dark red gradient, so a red ring
   against red artwork is nearly invisible from across a room. Contrast matters more than palette
   consistency for the one element telling you where you are. */
.poster-card:focus .poster-card-image-wrapper::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    border: 4px solid #fff;
    border-radius: inherit;
    pointer-events: none;
}

.poster-card:focus-visible .poster-card-image-wrapper::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    border: 4px solid #fff;
    border-radius: inherit;
    pointer-events: none;
}

/* The artwork shrinks by the ring's width so the ring sits beside the poster instead of covering it,
   and the card's total footprint is unchanged - nothing shifts when focus arrives.

   The size is stated with calc() rather than by setting all four offsets and letting the box stretch.
   That stretch trick only works for non-replaced elements: an <img> with `width: auto` resolves to its
   INTRINSIC width, so the four-offset version blew the poster up to its natural 400x600. calc() with
   subtraction is Chrome 26, so it is safe on the target engine. */
.poster-card:focus .poster-card-image {
    top: 4px;
    left: 4px;
    width: calc(100% - 8px);
    height: calc(100% - 8px);
}

.poster-card:focus-visible .poster-card-image {
    top: 4px;
    left: 4px;
    width: calc(100% - 8px);
    height: calc(100% - 8px);
}

/* --- growth on focus must be symmetric ------------------------------------------------------
   A focused card gets wider, and since its height is derived from its width the card also gets
   taller. In a row of differently-sized items the cross-axis alignment decides where that extra
   height goes: without an explicit value the card keeps its top edge and grows only downwards.
   Centring makes it expand evenly, which is what the desktop happened to do already.            */
.poster-row {
    align-items: center;
}

/* --- no scrollbars on a TV ------------------------------------------------------------------
   TV mode never scrolls the window - focused rows are brought into view by collapsing the rows
   already passed. Where a container does scroll (the library grid), a scrollbar is still just
   chrome that nothing can click. `scrollbar-width: none` is Firefox-only, so Blink needs the
   -webkit-scrollbar pseudo-element as well; without it the bar shows on the TV while looking
   fine in a desktop test that happens to use overlay scrollbars.                                */
body.tv-mode ::-webkit-scrollbar,
body.tv-mode::-webkit-scrollbar {
    width: 0;
    height: 0;
    display: none;
}

/* --- rows must never be squashed to fit --- */
/*
   In TV mode `main` is a column flex container whose content is taller than the viewport (three
   ~340px rows plus the 205px info bar against ~1016px of space). Flex items default to
   `flex-shrink: 1`, so the sections are candidates for shrinking. Modern engines refuse to shrink
   them below their content because a flex item's `min-height: auto` resolves to its content size -
   but that automatic minimum size arrived only around Chrome 46 and was unreliable in that era, so
   on the target runtime the sections are compressed instead and the posters spill into the row
   below. That is the rows "cutting each other out".

   Opting out of shrinking makes the intended behaviour explicit rather than relying on the engine's
   automatic minimum: rows keep their natural height, overflow simply extends off-screen, and the
   row-collapse scheme (setRowVisibility) is what brings the focused row into view. Collapsing still
   works, because it sets an inline `height: 0` which wins over a flex-basis of auto.
*/
.home-section {
    flex: 0 0 auto;
}

.poster-row {
    flex: 0 0 auto;
}

/* --- single-line flex containers: column axis --- */
.connect-page > * + * { margin-top: 0.75rem; }
.home-section > * + * { margin-top: 0.75rem; }
.player-page > * + * { margin-top: 1rem; }
.player-controls > * + * { margin-top: 0.75rem; }
.player-endcard-body > * + * { margin-top: 0.5rem; }

/* --- single-line flex containers: row axis --- */
.poster-row > * + * { margin-left: 1.25rem; }
.item-detail > * + * { margin-left: 2rem; }
.player-error > * + * { margin-left: 1rem; }
.player-transport > * + * { margin-left: 1.25rem; }
.player-osd > * + * { margin-left: 1rem; }
.player-menu-item > * + * { margin-left: 0.6rem; }
.player-endcard > * + * { margin-left: 1.5rem; }
.player-endcard-actions > * + * { margin-left: 0.75rem; }

/* .item-detail turns into a column at this width, so the spacing axis has to turn with it. */
@media (max-width: 720px) {
    .item-detail > * + * {
        margin-left: 0;
        margin-top: 2rem;
    }
}

/* --- wrapping containers: margin on every child, negative margin on the container --- */
.poster-grid,
.thumb-grid {
    margin-right: -1.25rem;
}

.poster-grid > *,
.thumb-grid > * {
    margin: 0 1.25rem 1.25rem 0;
}

/* Grid gave every column an equal share; flex needs the widths stated. These match the values
   .poster-row already uses, so a card is the same size in a row and in a grid. */
.poster-grid .poster-card {
    flex: 0 0 auto;
    width: 180px;
}

.thumb-grid .poster-card {
    flex: 0 0 auto;
    width: 320px;
}

/* --- fullscreen player: the .player-page column spacing must not apply --- */
.player-page.player-fullscreen > * + * {
    margin-top: 0;
}

/* --- replaces min() in max-width (Chrome 79) --- */
@media (max-width: 35rem) {
    .player-menu {
        max-width: 80vw;
    }
}

@media (max-width: 51rem) {
    .player-endcard {
        max-width: 90vw;
    }
}

/* --- Settings ------------------------------------------------------------------------------- */
.settings-page {
    max-width: 46rem;
}

.settings-section {
    margin-bottom: 2rem;
}

.settings-hint {
    color: var(--text-muted);
    max-width: 60ch;
}

/* The focus outline is the only cue on these controls, and it must read against the dark
   background - same reasoning as the poster cards. Separate rules, because a mixed
   :focus / :focus-visible list is dropped whole on the target engine. */
.settings-page .btn:focus {
    outline: 3px solid #fff;
    outline-offset: 2px;
}

.settings-page .btn:focus-visible {
    outline: 3px solid #fff;
    outline-offset: 2px;
}


.settings-list {
    max-width: 46rem;
}

.settings-list dt {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-top: 0.75rem;
}

.settings-list dd {
    margin: 0.15rem 0 0;
    word-break: break-all;
}

/* ---- Watched decoration ----
   Upper-right corner of the artwork: a checkmark once everything below the item is watched, or the
   number of unwatched episodes for a partly-watched season/series. Sits inside
   .poster-card-image-wrapper, which is the positioned, overflow-hidden box - the same anchor
   .poster-card-progress uses, so the badge scales with the card on focus instead of drifting. */
.poster-card-badge {
    position: absolute;
    top: 6px;
    right: 6px;
    min-width: 22px;
    height: 22px;
    padding: 0 5px;
    border-radius: 11px;
    background: rgba(22, 0, 0, 0.78);
    color: var(--bright-color);
    font-size: 12px;
    font-weight: 600;
    line-height: 22px;
    text-align: center;
    /* box-sizing so padding cannot push the pill past min-width into an oval on 1-digit counts. */
    box-sizing: border-box;
}

.poster-card-badge-watched {
    background: var(--brand-color);
    color: #fff;
    /* The icon is a 14px inline SVG; line-height centring does not apply to it, so the pill flips to
       a fixed-size box with the glyph nudged into the middle. `gap`/flex centring is avoided - flex
       is fine on Chromium 53 but `display: flex` on an inline-SVG parent measured inconsistently
       there, and a padding offset needs no layout support at all. */
    padding: 4px;
    line-height: 0;
}
