/* ============================================================
   vxm — mobile app shell (Netflix-style app experience on web)
   Everything is scoped to ≤991.98px (phones + tablets). Desktop
   is untouched. The native app webview never renders this markup
   (body.html gates on $is_mobileapp). Kill switch:
   config disable_mobile_web_ui = '1'.
   ============================================================ */

/* Inline Lucide icon (vodlix_lucide()) — inherits color, sized per use */
.vxm-ico {
    width: 1em;
    height: 1em;
    display: inline-block;
    vertical-align: -0.125em;
    flex-shrink: 0;
}
/* solid variant — Lucide is outline-only; play/CTA glyphs read better filled */
.vxm-ico.vxi-fill {
    fill: currentColor;
}

/* Long card titles marquee-scroll on one line instead of wrapping/clamping.
   The mobile JS (mobile_nav.html) adds .vxm-marquee + the scroll distance only
   when a title actually overflows; text-indent animation needs no inner span,
   so it survives Vue's v-html re-renders. */
.vxm-marquee {
    display: block !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: clip !important;
    -webkit-line-clamp: unset !important;
    animation: vxm-marquee var(--vxm-mq-dur, 12s) ease-in-out infinite;
    /* text-indent is a LAYOUT property, so this animation makes the browser
       re-run layout every frame — once per marqueeing title, forever. With
       dozens of overflowing titles on a rail-heavy page that is continuous
       main-thread work, which a high-end phone absorbs and a low-end one shows
       as scroll jitter. Containment scopes each recalculation to the title box
       so it can't force layout of the surrounding (very large) document.
       The element is already fixed-size + overflow:hidden, so nothing outside
       it depends on its internal layout. */
    contain: layout paint style;
}
@keyframes vxm-marquee {
    0%, 14%   { text-indent: 0; }
    46%, 62%  { text-indent: calc(-1 * var(--vxm-mq-dist, 0px)); }
    92%, 100% { text-indent: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .vxm-marquee { animation: none; }
}

/* Horizontal rails must never trap vertical page scrolling. Swiper 7's
   touchmove listener is non-passive and runs a JS angle test before deciding
   a gesture is "scrolling" — so page scrolls that start on a rail stutter
   (browser waits on JS every frame) and diagonal ones get claimed by the
   rail entirely (scroll goes dead). touch-action: pan-y hands vertical
   panning to the browser compositor immediately; Swiper only ever receives
   horizontal moves. Swiper 9+ ships this exact default — the bundled v7
   predates it. Unscoped on purpose: touch laptops at desktop widths hit the
   same trap. (v7 adds .swiper-horizontal at init; keep the legacy modifier
   for safety.) */
.swiper-horizontal,
.swiper-container-horizontal {
    touch-action: pan-y pinch-zoom;
}

/* Tab bar markup renders for every visitor — keep it out of the
   desktop flow entirely; the media query below reveals it. */
.vxm-tabbar {
    display: none;
}

/* ============================================================
   Profile & More screen (route /more) — app-style stacked rows.
   Base styles apply at every width (desktop just gets the same
   centered column); the media query only adds mobile tweaks.
   ============================================================ */

.vxm-more {
    max-width: 600px;
    margin: 0 auto;
    padding: 24px 16px 40px;
}

.vxm-more__title {
    font-size: 28px;
    font-weight: 700;
    color: #fff;
    margin: 0 0 24px;
}

.vxm-more__profileBlock {
    margin-bottom: 24px;
}

.vxm-more__profileLabel {
    color: #9b9ba3;
    font-size: 15px;
    margin-bottom: 14px;
}

.vxm-more__profile {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
}

/* horizontal switcher — all profiles, scrolls if many (max 5).
   Padding gives the current-profile outline room so overflow-x doesn't
   clip the ring; negative margin keeps the row flush with the edge. */
.vxm-more__profiles {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    padding: 5px 5px 4px;
    margin: -5px -5px 0;
    scrollbar-width: none;
}

.vxm-more__profiles::-webkit-scrollbar {
    display: none;
}

.vxm-more__profiles .vxm-more__profile {
    flex: 0 0 auto;
    border: 0;
    background: none;
    padding: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.vxm-more__profiles .vxm-more__profile--current .vxm-more__avatar {
    outline: 2px solid var(--vod-primary, #e50914);
    outline-offset: 2px;
}

.vxm-more__profiles .vxm-more__profile[disabled] {
    cursor: default;
}

.vxm-more__avatar {
    width: 64px;
    height: 64px;
    border-radius: 10px;
    object-fit: cover;
}

.vxm-more__avatar--empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.08);
    color: #cfcfd6;
    font-size: 34px;
}
.vxm-more__avatar--empty .vxm-ico {
    width: 34px;
    height: 34px;
    stroke-width: 1.5;
}

.vxm-more__profileName {
    color: #fff;
    font-weight: 600;
}

.vxm-more__manage {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-top: 18px;
    color: #cfcfd6;
    font-weight: 600;
    text-decoration: none;
}

.vxm-more__manage:hover {
    color: #fff;
    text-decoration: none;
}

.vxm-more__guest {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 24px;
}

.vxm-more__section {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 22px;
}

.vxm-more__heading {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #8a8a93;
    margin: 4px 0 2px;
}

.vxm-more__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.06);
    color: #fff;
    font-weight: 600;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}

.vxm-more__row:hover,
.vxm-more__row:focus {
    background: rgba(255, 255, 255, 0.1);
    color: #fff;
    text-decoration: none;
}

.vxm-more__row.text-danger:hover {
    color: #ff6b6b;
}

/* Language picker — native <details>, no JS toggle needed */
.vxm-more__langWrap {
    border-radius: 10px;
}

.vxm-more__rowSummary {
    cursor: pointer;
    list-style: none;
}

.vxm-more__rowSummary::-webkit-details-marker {
    display: none;
}

.vxm-more__langWrap[open] .vxm-more__rowSummary .mdi-chevron-down {
    transform: rotate(180deg);
}

.vxm-more__langList {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px 4px 2px;
}

.vxm-more__langBtn {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 12px 16px;
    border: 0;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.04);
    color: #cfcfd6;
    font-weight: 500;
    text-align: start;
    cursor: pointer;
}

.vxm-more__langBtn:hover {
    background: rgba(255, 255, 255, 0.09);
    color: #fff;
}

.vxm-more__langBtn.is-active {
    color: #fff;
    font-weight: 700;
}

/* Pills + category sheet are mobile-only chrome */
.vxm-pills,
.vxm-sheet {
    display: none;
}

/* App-like navigation feel: native cross-fade between page loads
   (View Transitions API — progressive; unsupported browsers just
   navigate normally). Scoped to all viewports on purpose: desktop
   benefits too and it cannot break layout. */
@view-transition {
    navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 0.22s;
}

@media (max-width: 991.98px) {

    /* Mobile page gutter. The platform's mobile chrome (the hero card, the
       pills strip) insets by 16px, but the rails pad themselves from
       --vxl-gutter, whose fallback is the desktop 3% — 11px on a 375px phone.
       That left the rails 5px inside the hero above them on the default
       layout. Declaring the token here puts everything on one edge.

       A layout that sets its own --vxl-gutter still wins: [data-layout="…"]
       outranks this body selector, so cinematic/grid (4%) and showcase (6%)
       keep their own mobile gutters. */
    body {
        --vxl-gutter: 16px;
    }

    :root {
        /* 64px to match the app's bottom-navigation (Figma node 30:115);
           its glyphs are 24px, the avatar 28px. Every consumer derives from
           this variable (bar height, content bottom padding), so one value
           keeps the bar and the page clearance in step. */
        --vxm-tabbar-h: 64px;
        --vxm-header-h: 68px;
    }

    /* ---- Netflix-style filter pills under the top bar -------- */
    .vxm-pills {
        position: fixed;
        top: var(--vxm-header-h);
        inset-inline: 0;
        z-index: 1029; /* just under the tab bar / header */
        display: flex;
        align-items: center;
        gap: 8px;
        /* the shared gutter token, so the chips line up with the rail
           headings and the hero copy rather than sitting 1px inside them */
        padding: 6px var(--vxl-gutter, 16px) 10px;
        transition: opacity 0.25s ease, transform 0.25s ease;
    }

    .vxm-pills.is-hidden {
        opacity: 0;
        transform: translateY(-10px);
        pointer-events: none;
    }

    /* many pills (Live TV + group pages) scroll horizontally like the app */
    .vxm-pills {
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
    }

    .vxm-pills::-webkit-scrollbar {
        display: none;
    }

    /* the pills' Categories sheet replaces the cateselector block
       (title + Genres dropdown) on the movies / tv-shows homes and
       on the per-category pages */
    body.page-movies .vxb,
    body.page-tv_shows .vxb,
    body.page-video_categories .vxb,
    body.page-series_categories .vxb,
    body.page-all-movies .vxb,
    body.page-all-tv-shows .vxb {
        display: none !important;
    }

    /* group pages: the pills carry the breadcrumb, so hide the server
       heading + breadcrumb row (the pills replace them) */
    body.page-content_list_group .breadcrumbs,
    body.page-content_list_group .insidePadding.mb-5.d-flex.align-items-center {
        display: none !important;
    }

    .vxm-pill {
        position: relative;
        display: inline-flex;
        align-items: center;
        gap: 4px;
        /* The app draws these chips 32px tall with 14px side padding and an
           8px gap (Figma `categories`, node 30:91). 32px is under the 44px tap
           minimum, so the chip stays 32px VISUALLY and grows an invisible 44px
           hit area via the ::after below — the design is matched and the
           target is still comfortable. */
        min-height: 32px;
        padding: 8px 14px;
        border: 1px solid var(--vxl-line-strong, rgba(255, 255, 255, 0.45));
        border-radius: 999px;
        background: var(--vxl-surface-1, rgba(10, 10, 15, 0.35));
        color: var(--vxl-ink, #fff);
        font-size: 13px;
        font-weight: 600;
        line-height: 1;
        text-decoration: none;
        white-space: nowrap;
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
    }

    /* invisible 44px tap area, centred on the 32px chip */
    .vxm-pill::after {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        top: 50%;
        height: 44px;
        transform: translateY(-50%);
    }

    .vxm-pill:hover,
    .vxm-pill:focus {
        color: #fff;
        text-decoration: none;
    }

    /* Rail / band "see everything" control is a bare chevron on phones, which
       is what the app shows — the words "View All" next to a heading cost a
       third of the row on a 375pt screen and say nothing the arrow doesn't.
       The label stays in the DOM for screen readers rather than being deleted. */
    .swiperHolder .exploreDiv > span,
    [data-layout="grid"] .vxlg-band__more > span,
    [data-layout="grid"] .vxlg-band__more {
        font-size: 0;
    }
    .swiperHolder .exploreDiv > span {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
    .swiperHolder .exploreDiv .vxm-ico,
    [data-layout="grid"] .vxlg-band__more svg {
        width: 22px !important;
        height: 22px !important;
        font-size: 22px !important;
        color: var(--vxl-ink);
    }

    /* Rail header — the app right-aligns "View All" to the content edge
       (Figma `section-header`, node 30:37: title at x=0, the link ending flush
       at 370), rather than parking it beside the title. */
    .swiperHolder .mb-2.d-flex.align-items-center > a,
    .swiperHolder .d-flex.align-items-center > a.whiteColor {
        margin-inline-start: auto;
    }

    /* Rail "View All" — a 20px-tall target sitting next to a heading. It is
       the only way into a full section from a phone, so give it a 44px hit
       area. The padding is offset by an equal negative margin, so the link
       grows for the thumb without pushing the heading row around. */
    .swiperHolder a.whiteColor.text-decoration-none,
    .exploreDiv {
        min-height: 44px;
    }
    .swiperHolder a.whiteColor.text-decoration-none {
        display: inline-flex;
        align-items: center;
        padding: 12px 8px;
        margin: -12px -8px;
    }

    /* The active pill is SOLID in the app — the tenant's foreground filled, its
       background as the label — not a slightly lighter version of the inactive
       ones. At 18% white the distinction was invisible in daylight, which is
       the only place a phone gets used. Derived, so it inverts correctly on a
       light palette instead of being a hard-coded white chip. */
    .vxm-pill.is-active {
        background: var(--vxl-ink) !important;
        border-color: var(--vxl-ink) !important;
        color: var(--vod-body-bg, #000) !important;
        font-weight: 600;
    }

    .vxm-pill--reset {
        padding: 7px 9px;
    }

    .vxm-pill--menu .mdi,
.vxm-pill--menu .vxm-ico {
        font-size: 15px;
        line-height: 1;
    }

    /* ---- Categories bottom sheet ----------------------------- */
    .vxm-sheet.is-open {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 1040; /* above tab bar, below Bootstrap modals */
    }

    .vxm-sheet__backdrop {
        position: absolute;
        inset: 0;
        background: rgba(0, 0, 0, 0.6);
    }

    .vxm-sheet__panel {
        position: absolute;
        bottom: 0;
        inset-inline: 0;
        max-height: 75dvh;
        display: flex;
        flex-direction: column;
        padding: 20px 16px calc(16px + env(safe-area-inset-bottom, 0px));
        border-radius: 16px 16px 0 0;
        background: #17171c;
    }

    .vxm-sheet__title {
        font-size: 17px;
        font-weight: 700;
        color: #fff;
        margin-bottom: 12px;
    }

    .vxm-sheet__list {
        overflow-y: auto;
        display: flex;
        flex-direction: column;
    }

    .vxm-sheet__link {
        padding: 13px 4px;
        color: #cfcfd6;
        font-size: 16px;
        font-weight: 500;
        text-decoration: none;
        border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    }

    .vxm-sheet__link:hover,
    .vxm-sheet__link:focus {
        color: #fff;
        text-decoration: none;
    }

    .vxm-sheet__closeBtn {
        align-self: center;
        margin-top: 14px;
        width: 44px;
        height: 44px;
        border: 0;
        border-radius: 50%;
        background: #fff;
        color: #000;
        font-size: 22px;
        cursor: pointer;
    }

    /* ---- bottom tab bar ---------------------------------- */
    .vxm-tabbar {
        position: fixed;
        bottom: 0;
        inset-inline: 0;
        z-index: 1030; /* below Bootstrap modals (1050) */
        display: flex;
        align-items: stretch;
        height: calc(var(--vxm-tabbar-h) + env(safe-area-inset-bottom, 0px));
        padding-bottom: env(safe-area-inset-bottom, 0px);
        background: var(--mobile-bottomNavigationBarColor,
                    var(--vod-body-back-color, #0b0b0f));
        border-top: 1px solid rgba(255, 255, 255, 0.08);
        /* own compositor layer: keeps the fixed bar stable while rails
           and the hero trailer animate behind it */
        transform: translateZ(0);
    }

    /* Icon-forward bar (no labels): bigger glyphs, state carried by tint +
       weight — the Apple way. Labels stay in the a11y tree (visually hidden,
       see below), never display:none. */
    .vxm-tabbar__item {
        flex: 1 1 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 0;
        min-width: 0;
        color: #8a8a93;
        line-height: 1;
        text-decoration: none;
        -webkit-tap-highlight-color: transparent;
    }

    .vxm-tabbar__item:hover,
    .vxm-tabbar__item:focus {
        color: #cfcfd6;
        text-decoration: none;
    }

    .vxm-tabbar__item.is-active {
        color: #fff;
    }

    .vxm-tabbar__item i {
        font-size: 28px;
        line-height: 1;
    }
    .vxm-tabbar__item .vxm-ico {
        width: 28px;
        height: 28px;
    }
    /* active tab: crisp white + a hair heavier stroke (Lucide is stroke-only) */
    .vxm-tabbar__item.is-active .vxm-ico {
        stroke-width: 2.3;
    }

    /* labels removed visually, kept for screen readers (each tab still named) */
    .vxm-tabbar__item span {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }

    .vxm-tabbar__avatar {
        width: 30px;
        height: 30px;
        border-radius: 8px;
        object-fit: cover;
    }

    .vxm-tabbar__item.is-active .vxm-tabbar__avatar {
        outline: 2px solid #fff;
        outline-offset: 2px;
    }

    /* Content must never hide behind the fixed bar. The clearance goes on the
       in-flow content wrapper, NOT on body: body is the scroll container here
       (vodlix.css sets overflow-x:hidden, which promotes overflow-y to auto),
       and a scroll container's OWN padding-bottom is dropped from the scrollable
       area — iOS Safari especially — so body padding left the last row hidden
       behind the bar. .contentHolder is a normal in-flow block, so its bottom
       padding reliably reserves the space. :has() keeps it off the pages where
       body.html renders no bar at all (login, signup, profiles gate, webview).
       +16px so the last row has breathing room and isn't read as clipped. */
    body:has(.vxm-tabbar) .contentHolder {
        padding-bottom: calc(var(--vxm-tabbar-h) + env(safe-area-inset-bottom, 0px) + 16px);
    }

    /* The giant transparent LCP-hack image makes Chromium paint a
       ghost duplicate of the fixed tab bar near the top of the
       viewport (its 99vw×99vh composited layer captures the bar's
       first paint). Verified: removing the image removes the ghost.
       The LCP trick is dispensable on mobile. */
    img.vx-lcp-hack {
        display: none;
    }

    /* ---- hide website chrome (app look) ------------------- */
    /* Footer */
    footer.vxf,
    .vxf {
        display: none !important;
    }

    /* Burger + drawer are replaced by the More tab; the language picker moves
       into Profile & More. Search and the account avatar STAY — the app's home
       screen has both in the top bar, and hiding them left a header that was a
       logo and 350px of nothing. */
    .vxh__burger,
    .vxh-drawer,
    .vxh__lang {
        display: none !important;
    }

    /* ---- Top bar: logo left, search + avatar right (app parity) --------- */
    .vxh__search,
    .vxh__user {
        display: flex !important;
        align-items: center;
    }
    /* Push them to the right edge: the nav that normally separates the logo
       from these is hidden on mobile, so without this they sit against it. */
    .vxh__spacer {
        display: block !important;
        flex: 1 1 auto !important;
    }
    .vxh__search .vxh__iconBtn,
    .vxh__user .vxh__iconBtn {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 44px;
        height: 44px;
        padding: 0;
        border: 0;
        background: none;
        color: var(--vxl-ink);
    }
    .vxh__search .vxh__iconBtn .vxm-ico,
    .vxh__search .vxh__iconBtn svg {
        width: 24px !important;
        height: 24px !important;
    }
    /* Avatar only — the name and caret belong to the desktop bar and would eat
       the width the pills strip needs. */
    .vxh__user .vxh__userName,
    .vxh__user .vxh__caret {
        display: none !important;
    }
    .vxh__user .vxh__avatar {
        width: 30px;
        height: 30px;
        border-radius: 50%;
        object-fit: cover;
    }
    /* The expanding search box is a desktop affordance (it grows into the nav's
       empty space). On a phone there is no space to grow into and there is a
       dedicated Search tab, so the icon simply goes there. */
    .vxh__search .vxh__searchBox {
        display: none !important;
    }

    /* ---- Search screen — app-style big rounded input -------- */
    /* Redundant "All results / <keyword>" heading (also masks the
       pre-existing "/ null" empty-query cosmetic bug on mobile) */
    body.page-search #searchPage h2.h3 {
        display: none;
    }

    body.page-search #searchPage .text-right {
        text-align: start !important;
    }

    body.page-search #searchPage .serachBlock {
        flex-direction: column;
        justify-content: flex-start;
        gap: 12px;
    }

    body.page-search #searchPage .searchHolder {
        /* vodlix.css pins .searchHolder to width:18%!important (header widget
           sizing) — the search page input must be full-bleed like the app.
           position:relative so the magnify button anchors to the field box
           (NOT display:flex — that stretched the holder taller than the input
           and floated the icon above it). */
        width: 100% !important;
        position: relative;
        overflow: visible;
        text-align: start;
    }

    body.page-search #searchPage .searchBlock {
        width: 100%;
        /* .searchBlock wraps only the input (52px), so a ::before glyph
           anchored here centres reliably — .searchHolder is taller and
           floated the standalone icon above the field */
        position: relative;
    }

    body.page-search #searchPage .searchBlock::before {
        content: "\F0349"; /* mdi-magnify */
        font-family: "Material Design Icons";
        position: absolute;
        inset-inline-start: 14px;
        top: 50%;
        transform: translateY(-50%);
        z-index: 2;
        color: #9b9ba3;
        font-size: 20px;
        line-height: 1;
        pointer-events: none;
    }

    body.page-search #searchPage .searchBlock input.form-control {
        width: 100%;
        height: 52px;
        border: 0;
        border-radius: 10px;
        /* Derived, not hardcoded: a literal white fill and `color: #fff` gave a
           light tenant palette white text on a near-white field. */
        background-color: var(--vxl-surface-2);
        color: var(--vxl-ink);
        font-size: 16px;
        /* leave room for the magnify button pinned on the left */
        padding-inline: 46px 16px;
    }

    /* the standalone magnify button is redundant once the glyph lives on the
       field wrapper's ::before */
    body.page-search #searchPage .searchBtn1 {
        display: none;
    }

    body.page-search #searchPage .btn-group {
        display: flex;
        gap: 8px;
        margin-left: 0 !important;
        width: 100%;
    }

    body.page-search #searchPage .btn-group .btn {
        flex: 1 1 0;
        min-width: 0;
        border-radius: 999px !important;
        border: 1px solid rgba(255, 255, 255, 0.25);
        background: rgba(255, 255, 255, 0.08);
    }

    /* recommended-suggestions heading (empty search state) */
    body.page-search .vxm-search-suggest-head {
        color: #fff;
        font-size: 17px;
        font-weight: 700;
        margin: 6px 0 12px;
    }

    /* ---- My List / content-list screens ---------------------- */
    body.page-content_list #lists h2.h3 {
        text-align: center;
        font-weight: 600 !important;
    }

    body.page-content_list #lists .text-center.pt-4 {
        padding: 90px 16px;
        color: #9b9ba3;
        font-size: 16px;
    }

    /* ---- Editors-pick hero: app-style boxed card ------------- */
    /* Design mode comes from config('mobile_editors_pick_design') —
       the same knob the native app reads. The component adds
       .vxe-mob-portrait (default: still 2:3 card, no trailer) or
       .vxe-mob-landscape (16:9 card, trailer allowed, info below). */

    /* Shared card frame — only on the home screens where the hero is
       the first thing under the transparent header + pills */
    body.page-home #new-editorpick,
    body.page-movies #new-editorpick,
    body.page-tv_shows #new-editorpick,
    body.page-content_list_group #new-editorpick {
        margin-top: calc(var(--vxm-header-h) + 58px);
        margin-inline: 16px !important; /* swiper-container sets its own margins */
        width: calc(100% - 32px) !important;
        border-radius: 16px;
        /* the featured swiper forces overflow:visible for the desktop
           billboard bleed — that leaks the next slide past the card */
        overflow: hidden !important;
    }

    /* Group hero: it renders inside #vxe-ep-mount > .vxe-ep-standalone, which
       carries a -42px desktop-overlap margin that swallows the card's own
       margin-top (and margin-collapses through the mount). So clear the
       header+pills on the MOUNT (padding, collapse-proof) and neutralise the
       negative overlap + the card margin here. */
    body.page-content_list_group #vxe-ep-mount {
        padding-top: calc(var(--vxm-header-h) + 58px);
    }
    body.page-content_list_group #new-editorpick {
        margin-top: 0 !important;
    }
    body.page-content_list_group .featureSlider.vxe-ep-standalone {
        margin-top: 0 !important;
    }

    /* -- portrait: Netflix-style 2:3 poster card ---------------- */
    /* the featured swiper sizes slides for the desktop billboard
       (fixed width/height) — pin everything to the card box */
    .vxe-mob-portrait .swiper-slide {
        width: 100% !important;
        height: auto !important;
    }

    .vxe-mob-portrait .swiper-slide .relative,
    .vxe-mob-portrait .relative {
        height: auto !important;
    }

    .vxe-mob-portrait .ratio-16x9,
    .vxe-mob-portrait .ratio.ratio-16x9 {
        /* Near-square, not 2:3. Measured off the app's portrait home screen:
           the card is a crop of the poster, and a full 2:3 card ran past the
           fold — the genre line and buttons fell off the bottom of the phone. */
        aspect-ratio: 1 / 1;
        border-radius: 16px;
        overflow: hidden;
        width: 100% !important;
        height: auto !important; /* billboard CSS pins a viewport height */
        padding-top: 0 !important;
        padding-bottom: 0 !important; /* the ratio classes use padding hacks */
    }

    /* NOTE: an earlier revision gave slides with no portrait artwork a 16:9 card
       so the landscape fallback wasn't hard-cropped. That has been reverted on
       purpose — a carousel whose slides are different heights desyncs
       everything keyed to --vxe-media-h (the dot strip, the space reserved for
       it), and the page visibly jumped between picks. One height for every
       slide; a title with no poster gets a centre crop, which is ordinary. The
       real fix is uploading portrait art, not bending the layout around its
       absence. The `.vxe-noPortraitArt` class is still emitted and is the hook
       if that ever needs revisiting. */

    .vxe-mob-portrait .ratio-16x9 img,
    .vxe-mob-portrait .ratio.ratio-16x9 img {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    /* Soft bottom wash inside the card so the genre line overlaid on it (see
       the .vxe-genres rule below) stays readable on light artwork. Drawn on
       the ratio box — it clips to the card's radius — and not on `.relative`,
       whose scrims are killed above because it also spans the buttons. Scoped
       away from Cinematic, which paints its own hero scrims. */
    body:not([data-layout="cinematic"]) .vxe-mob-portrait .ratio-16x9::after,
    body:not([data-layout="cinematic"]) .vxe-mob-portrait .ratio.ratio-16x9::after {
        content: "";
        position: absolute;
        inset-inline: 0;
        bottom: 0;
        height: 96px;
        z-index: 1;
        pointer-events: none;
        background: linear-gradient(0deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0) 100%);
    }

    /* the slide carries an xs-only fallback button block below the
       artwork (pre-app-shell design); the billboard overlay is the
       real UI in both card modes — kill the duplicate */
    .vxe-mob-portrait .swiper-slide > div.d-lg-none.d-md-none.d-sm-none,
    .vxe-mob-landscape .swiper-slide > div.d-lg-none.d-md-none.d-sm-none {
        display: none !important;
    }

    /* portrait: no trailer — hide the video + its controls */
    .vxe-mob-portrait video,
    .vxe-mob-portrait .vxe-controls {
        display: none !important;
    }

    /* the hero art is tappable → More Info (bound in editorpick.html);
       position:relative so the logo overlay anchors to the card */
    .vxe-mob-portrait .relative,
    .vxe-mob-landscape .relative {
        cursor: pointer;
        position: relative !important;
    }

    /* -- landscape: 16:9 card, info block below (native app look) */
    .vxe-mob-landscape .swiper-slide {
        width: 100% !important;
        height: auto !important;
    }

    .vxe-mob-landscape .swiper-slide .relative,
    .vxe-mob-landscape .relative {
        height: auto !important;
    }

    .vxe-mob-landscape .ratio-16x9,
    .vxe-mob-landscape .ratio.ratio-16x9 {
        /* A true 16:9 card. An earlier Figma frame had this at 370x323, but the
           shipped app screen for the LANDSCAPE thumb type is 16:9 — the taller
           crop belongs to the portrait type, which has its own card below. */
        aspect-ratio: 16 / 9;
        width: 100% !important;
        height: auto !important;
        padding-top: 0 !important;
        padding-bottom: 0 !important;
        border-radius: 16px;
        overflow: hidden;
    }

    .vxe-mob-landscape .ratio-16x9 img,
    .vxe-mob-landscape .ratio.ratio-16x9 img,
    .vxe-mob-landscape .ratio-16x9 video,
    .vxe-mob-landscape .ratio.ratio-16x9 video {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    .vxe-mob-landscape .swiperInfo.vxe-billboard {
        position: static !important;
        background: none !important;
        padding: 14px 4px 0 !important;
        min-height: 0 !important;
    }

    /* overlay scrims make no sense once the info sits below the card — and
       since .relative now spans the buttons too, the bottom scrim was
       darkening the poster AND dimming the Play/More Info buttons */
    .vxe-mob-landscape .relative::before,
    .vxe-mob-landscape .relative::after,
    .vxe-mob-portrait .relative::before,
    .vxe-mob-portrait .relative::after {
        display: none !important;
    }

    /* ---- shared hero (portrait + landscape) app-style ------- */
    /* content logo overlaid top-left on the artwork (instead of a
       centered title); the info block (buttons) sits below the card */
    .vxe-mob-portrait .swiperInfo.vxe-billboard,
    .vxe-mob-landscape .swiperInfo.vxe-billboard {
        position: static !important;
        background: none !important;
        min-height: 0 !important;
        padding: 12px 0 0 !important; /* buttons span the full card width */
    }

    /* The logo + mute sit at the BOTTOM of the media card. They're siblings
       of the media inside .relative, which also spans the buttons below — so
       anchor them to the media's bottom using the card's deterministic 16:9
       height (card width = viewport − 32px inset). */
    .vxe-mob-landscape,
    .vxe-mob-landscape .relative {
        --vxe-media-h: calc((100vw - 32px) * 0.5625);
    }
    .vxe-mob-portrait,
    .vxe-mob-portrait .relative {
        /* Measured off the app's portrait home screen: the hero card is very
           close to square, not the 2:3 a poster would be — the artwork is a
           crop, so a full 2:3 card would tower over the fold. */
        --vxe-media-h: calc(100vw - 32px);
    }

    /* Bootstrap makes .row/.col position:relative — force static so the
       absolutely-positioned logo below anchors to .relative (the card),
       not its column */
    .vxe-mob-portrait .swiperInfo .row,
    .vxe-mob-landscape .swiperInfo .row,
    .vxe-mob-portrait .swiperInfo [class*="col-"],
    .vxe-mob-landscape .swiperInfo [class*="col-"] {
        position: static !important;
        align-items: flex-start !important;
        /* full-width buttons — kill Bootstrap row/col gutters */
        padding-inline: 0 !important;
        margin-inline: 0 !important;
        max-width: 100% !important;
        flex: 0 0 100% !important;
    }

    /* logo overlay: landscape only — the backdrop/trailer has no title, so
       the content logo goes bottom-left. Portrait posters already bake in the
       title/logo, so overlaying a second one just duplicates it. */
    /* No logo overlay in either mode. The app titles the landscape hero in TEXT
       below the artwork (next to the Play button) and lets the portrait poster
       carry its own branding — in both cases a logo burned onto the picture is
       a third title competing with the artwork's own. */
    .vxe-mob-portrait .slideLogo,
    .vxe-mob-landscape .slideLogo {
        display: none !important;
    }

    /* mute: bottom-right of the media card (landscape only) */
    .vxe-mob-landscape .vxe-controls {
        top: calc(var(--vxe-media-h) - 52px) !important;
        bottom: auto !important;
        inset-inline-end: 12px;
    }
    .vxe-mob-portrait .slideLogo img.innerSlideLogo,
    .vxe-mob-landscape .slideLogo img.innerSlideLogo {
        max-height: 46px;
        max-width: 100%;
        width: auto;
        object-fit: contain;
    }

    /* ---- Landscape hero info: title + subtitle left, Play right ---------
       Selectors carry `.vxe-billboard` for weight, same as the hero action row
       further down: editorpick.css styles these as
       `.featureSlider .vxe-billboard .slideTitle` (three classes), so a
       two-class rule here loses every property they share no matter how late it
       loads. Three ties, and mobile-app.css loading last settles it.
       The app lays the block under the artwork out as a two-column row — the
       title over a one-line subtitle on the left, a single Play button pinned
       right and vertically centred against both. A grid does that without
       needing the markup regrouped: the text stacks in column one, the button
       spans both rows in column two. */
    .vxe-mob-landscape .vxe-billboard .row > [class*="col-"] {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-rows: auto auto;
        align-items: center;
        column-gap: 16px;
        row-gap: 5px;
        padding-block: 16px 4px !important;
    }
    /* The stock title only exists in the no-logo branch; `.vxe-mobTitle` is the
       mobile one that always renders. Hide the stock one so items without logo
       art don't get two. */
    .vxe-mob-landscape .vxe-billboard .slideTitle:not(.vxe-mobTitle),
    .vxe-mob-portrait .vxe-billboard .slideTitle:not(.vxe-mobTitle) {
        display: none !important;
    }
    .vxe-mob-landscape .vxe-billboard .vxe-mobTitle {
        grid-column: 1;
        grid-row: 1;
        margin: 0 !important;
        font-size: 18px !important;
        font-weight: 700;
        line-height: 1.3;
        letter-spacing: -0.01em;
        text-transform: none !important;
        /* two lines maximum: a long title must not push the Play button down */
        display: -webkit-box !important;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }
    /* Landscape subtitle is the synopsis; portrait uses the genre line (below),
       which is what each of the two app screens shows. */
    .vxe-mob-landscape .vxe-billboard .vxe-genres {
        display: none !important;
    }
    .vxe-mob-landscape .vxe-billboard .text_description {
        display: block !important;
        grid-column: 1;
        grid-row: 2;
        margin: 0 !important;
        font-size: 13px;
        line-height: 1.4;
        color: var(--vxl-ink-muted);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .vxe-mob-landscape .vxe-billboard .text_description1 {
        display: none !important;
    }
    .vxe-mob-landscape .vxe-billboard .sldieBtns {
        grid-column: 2;
        grid-row: 1 / span 2;
        /* The shared hero-action rules make every button `flex: 1 1 0`, which is
           right when they fill the row but wrong in a grid cell — it let Play
           shrink to whatever the title left over (72px, jammed against the
           text). Pin a real size instead. */
        flex: 0 0 auto !important;
        width: auto !important;
    }
    .vxe-mob-landscape .vxe-billboard .sldieBtns .btn {
        flex: 0 0 auto !important;
        min-width: 112px;
        height: 48px !important;
        padding: 0 20px !important;
        font-size: 15px !important;
    }

    /* ---- Portrait hero -------------------------------------------------
       The app's portrait screen is a near-square poster card, a centred genre
       line, then two buttons. The poster bakes in the title, so no title text
       under it, and the synopsis is not shown at all — the genres are the
       whole second line. */
    .vxe-mob-portrait .vxe-billboard .vxe-mobTitle,
    .vxe-mob-portrait .vxe-billboard .text_description,
    .vxe-mob-portrait .vxe-billboard .text_description1 {
        display: none !important;
    }
    /* The genre line sits ON the artwork, centred over its bottom edge — as a
       grey caption floating between the dot strip and the buttons it read as
       debris that fell out of the card. Scoped away from Cinematic, which
       overlays its whole info block on the artwork and styles this line
       itself; anchored to `.relative` (the rows/cols are forced static above)
       so it rides the card regardless of what the info block below does.
       pointer-events off so the artwork tap-through (bindMobileHeroTap) still
       lands on the media under it. */
    body:not([data-layout="cinematic"]) .vxe-mob-portrait .vxe-billboard .vxe-genres {
        display: block !important;
        position: absolute;
        top: calc(var(--vxe-media-h) - 36px);
        inset-inline: 20px;
        z-index: 3;
        margin: 0 !important;
        font-size: 13px;
        line-height: 1.4;
        color: #fff;
        text-align: center;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
        pointer-events: none;
    }
    /* Buttons: a centred pair — My List (inverted) then Play (accent). The app
       shows exactly these two; Trailer and More Info are dropped because
       tapping the artwork already opens More Info (bindMobileHeroTap) and
       Trailer belongs to the detail screen. */
    .vxe-mob-portrait .vxe-billboard .sldieBtns {
        display: flex !important;
        justify-content: center;
        gap: 12px;
        margin-top: 14px;
    }
    .vxe-mob-portrait .vxe-billboard .sldieBtns .btn-secondary,
    .vxe-mob-portrait .vxe-billboard .sldieBtns a:has(.mdi-play-speed),
    .vxe-mob-portrait .vxe-billboard .sldieBtns a[data-stats-event="trailer_play"] {
        display: none !important;
    }
    .vxe-mob-portrait .vxe-billboard .sldieBtns .btn,
    .vxe-mob-portrait .vxe-billboard .sldieBtns .vxe-mylist {
        flex: 0 1 152px !important;
        min-width: 0;
        height: 44px !important;
        margin: 0 !important;
        padding: 0 12px !important;
        border-radius: 6px;
        font-size: 15px !important;
        font-weight: 700 !important;
    }
    /* My List sits first, Play second — the app's order, and the destructive-
       free action reads better on the left. */
    .vxe-mob-portrait .vxe-billboard .sldieBtns .vxe-mylist {
        order: -1;
    }

    /* ---- My List pill --------------------------------------------------
       The favourites components render a bare icon toggle; the app wants an
       icon + label pill. Wrapping rather than forking the component keeps one
       source of truth for the add/remove behaviour and the check/plus state. */
    .vxe-mylist {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 8px;
        background: var(--vxl-ink);
        color: var(--vod-body-bg, #000);
        border: 0;
        white-space: nowrap;
    }
    .vxe-mylist > .boxBtn,
    .vxe-mylist > button {
        width: auto !important;
        min-width: 0 !important;
        height: auto !important;
        min-height: 0 !important;
        margin: 0 !important;
        padding: 0 !important;
        border: 0 !important;
        border-radius: 0 !important;
        background: none !important;
        box-shadow: none !important;
        color: inherit !important;
    }
    .vxe-mylist .vxm-ico {
        width: 20px !important;
        height: 20px !important;
        color: inherit !important;
    }
    .vxe-mylist__label {
        font-size: 15px;
        font-weight: 700;
        line-height: 1;
    }
    /* Landscape hero keeps its single Play button — the app's landscape screen
       has no My List there. */
    .vxe-mob-landscape .vxe-billboard .vxe-mylist {
        display: none !important;
    }

    /* ---- Carousel dots -------------------------------------------------
       Centred under the artwork, active dot in the tenant's accent. Swiper
       supplies the bullets; only the sizing and colour are ours. */
    /* Swiper renders the pagination as a sibling of `.swiper-wrapper`, so in
       normal flow it lands BELOW the entire slide — under the buttons, next to
       the following rail, which is where it first appeared. The app puts it
       directly under the artwork. The media height is a known quantity
       (--vxe-media-h, derived from the viewport and the card's aspect), so the
       dots can be pinned there exactly; the info block reserves the matching
       space below so nothing overlaps. */
    #new-editorpick {
        position: relative;
    }
    /* Keyed on `.vxe-dots` alone, not `.swiper-pagination`: Swiper adds its
       modifier classes (…-bullets, …-clickable, …-horizontal) but expects the
       BASE class to already be in your markup, and ours only carried the
       theme's own hook. Depending on a class the library doesn't add is how
       this silently stayed `position: static`. */
    .vxe-dots {
        position: absolute !important;
        top: calc(var(--vxe-media-h) + 10px);
        inset-inline: 0;
        bottom: auto !important;
        z-index: 4;
        display: flex;
        justify-content: center;
        align-items: center;
        gap: 6px;
        margin: 0;
    }
    /* Room for the dots between the artwork and the info block. */
    .vxe-mob-portrait .swiperInfo.vxe-billboard,
    .vxe-mob-landscape .swiperInfo.vxe-billboard {
        padding-top: 26px !important;
    }
    /* A single pick has nothing to page through — Swiper locks the element, and
       the reserved space would then be a gap for no reason. */
    #new-editorpick:has(.swiper-pagination-lock) .swiperInfo.vxe-billboard {
        padding-top: 12px !important;
    }
    /* Swiper hides a locked pagination with a bare `.swiper-pagination-lock
       { display: none }` — which the `display: flex` above out-cascades (same
       specificity, this sheet loads later). Restate the lock, or a single
       pick shows one orphan dot floating under the card. */
    .vxe-dots.swiper-pagination-lock {
        display: none !important;
    }
    .vxe-dots .swiper-pagination-bullet {
        width: 6px;
        height: 6px;
        margin: 0 !important;
        border-radius: 50%;
        background: var(--vxl-ink-muted);
        opacity: 0.45;
        transition: opacity var(--vxl-dur-2) ease, background-color var(--vxl-dur-2) ease;
    }
    .vxe-dots .swiper-pagination-bullet-active {
        background: var(--vod-primary, #e50914);
        opacity: 1;
    }
    /* the dot itself is 6px; give the tap target the full 44 without moving it */
    .vxe-dots .swiper-pagination-bullet::after {
        content: "";
        position: absolute;
        inset: -19px -6px;
    }
    .vxe-dots .swiper-pagination-bullet {
        position: relative;
    }

    /* Only Play in the hero. The app's landscape screen has a single CTA — More
       Info is redundant here because tapping the artwork already opens it (see
       bindMobileHeroTap), and Trailer belongs to the detail screen. */
    .vxe-mob-landscape .vxe-billboard .sldieBtns .btn-secondary,
    .vxe-mob-landscape .vxe-billboard .sldieBtns a:has(.mdi-play-speed),
    .vxe-mob-landscape .vxe-billboard .sldieBtns a[href*="/watch/"]:has(.vxm-ico.iconsize):not(:has(.vxi-fill)) {
        display: none !important;
    }

    /* ---- Hero actions: parity with the native app ----------------------
       Measured off the app design (Figma `hero-actions`, node 30:102):
       three equal buttons filling the 370px content width, 44px tall, 6px
       apart, 6px radius, 14px bold labels with a 16px icon 8px from the text.

       The Trailer button used to be hidden here. The app shows it, so it is
       restored — the row is three-up in the design, and dropping one made the
       remaining two stretch to a width the design never has.

       Selectors carry `.vxe-billboard` purely for weight: editorpick.css
       styles this row as `.featureSlider .vxe-billboard .sldieBtns` (three
       classes), so a two-class rule here lost every shared property no matter
       how late it loaded. Three classes ties, and source order — mobile-app.css
       loads after editorpick.css — settles the tie in favour of app parity.
       The `!important` on font-size is not decoration either: vodlix.css
       declares `.sldieBtns .btn { font-size: 1rem !important }` under 767px,
       and nothing but !important outranks !important. */
    .vxe-mob-portrait .vxe-billboard .sldieBtns,
    .vxe-mob-landscape .vxe-billboard .sldieBtns {
        display: flex !important;
        flex-wrap: nowrap;
        gap: 6px;
        width: 100%;
        margin-top: 0;
    }
    /* The row is also a Bootstrap `.clearfix`, and once it becomes a flex
       container that `::after` stops clearing floats and becomes a third,
       zero-width flex ITEM — which still claims a `gap` after the last button.
       The buttons ended 6px short of the right gutter for exactly that reason,
       which reads as a misalignment against the artwork above. */
    .vxe-mob-portrait .vxe-billboard .sldieBtns::after,
    .vxe-mob-landscape .vxe-billboard .sldieBtns::after {
        display: none;
    }

    .vxe-mob-portrait .vxe-billboard .sldieBtns .btn,
    .vxe-mob-landscape .vxe-billboard .sldieBtns .btn {
        flex: 1 1 0;
        min-width: 0;
        height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 8px;
        border-radius: 6px !important;
        font-size: 14px !important;
        font-weight: 700 !important;
        white-space: nowrap;
        padding: 0 8px !important;
        margin: 0 !important;
    }
    /* Labels ellipsis rather than push the row past the gutter — a localized
       build ("Plus d'informations") is a third longer than the English. */
    .vxe-mob-portrait .vxe-billboard .sldieBtns .btn span,
    .vxe-mob-landscape .vxe-billboard .sldieBtns .btn span {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .vxe-mob-portrait .vxe-billboard .sldieBtns .btn .vxm-ico,
    .vxe-mob-landscape .vxe-billboard .sldieBtns .btn .vxm-ico,
    .vxe-mob-portrait .vxe-billboard .sldieBtns .btn i,
    .vxe-mob-landscape .vxe-billboard .sldieBtns .btn i {
        flex: none;
        font-size: 16px !important;
        line-height: 1;
        margin: 0 !important;
    }

    /* Trailer + Play — the tenant's accent (the design's "secondary button"
       variable, #e4000e, is that brand red) */
    .vxe-mob-portrait .vxe-billboard .sldieBtns .btn-editors-pick,
    .vxe-mob-landscape .vxe-billboard .sldieBtns .btn-editors-pick,
    .vxe-mob-portrait .vxe-billboard .sldieBtns a:has(.mdi-play-speed),
    .vxe-mob-landscape .vxe-billboard .sldieBtns a:has(.mdi-play-speed) {
        background: var(--vod-primary, #e50914) !important;
        border: 0 !important;
        color: var(--vod-btn-primary-text, #fff) !important;
    }

    /* More Info — the platform's own secondary-button variable already
       defaults to the exact grey the design uses, rgba(109,109,110,0.7) */
    .vxe-mob-portrait .vxe-billboard .sldieBtns .btn-secondary,
    .vxe-mob-landscape .vxe-billboard .sldieBtns .btn-secondary {
        background: var(--vod-ep-secondary-button-bg, rgba(109, 109, 110, 0.7)) !important;
        border: 0 !important;
        color: var(--vod-ep-secondary-button-text, #fff) !important;
    }

    /* ---- Details screens (movie / series) — app-style ------- */
    /* Full-bleed card, Netflix-style full-width play CTA with the
       action circles (my list / rate / share) spread beneath it. */
    body.page-video_details .detailsContainer,
    body.page-series_details .detailsContainer {
        padding-inline: 0 !important;
        max-width: 100%;
    }

    /* The dedicated detail PAGE renders the block as `.videoDetails.detail`
       (rgb(18,18,18) + 8px radius) — a raised card that reads right in the
       modal but wrong on a full page. On mobile it IS the page, so drop the
       surface entirely and let it sit flush on the black background. (Targets
       .videoDetails directly — the page variant has no `.card` class.) */
    body.page-video_details .detailsContainer .videoDetails,
    body.page-series_details .detailsContainer .videoDetails,
    body.page-video_details .detailsContainer .card,
    body.page-series_details .detailsContainer .card {
        background: transparent !important;
        border: 0 !important;
        border-radius: 0 !important;
        box-shadow: none !important;
    }

    /* Movie details: the block overlays the buttons/meta (.overlapData) on a
       stretched .detailsThumb — .overlapData has `margin:-8vw 0 0; height:100%`
       and the ratio is forced `height:100%`, which leaves a big black gap
       between the 16:9 media and the bottom-anchored content. On mobile, make
       the media a real 16:9 box and let the content flow below it. */
    body.page-video_details .detailsThumb {
        height: auto !important;
    }
    body.page-video_details .detailsThumb .ratio-16x9 {
        height: auto !important;
        aspect-ratio: 16 / 9;
        padding-top: 0 !important;
        border-radius: 12px;
        overflow: hidden;
    }
    /* Keep the bottom shade (gradient) — the title logo now overlays the media
       bottom like the series / editors-pick hero and needs it for contrast;
       only the side shade is dropped. */
    body.page-video_details .detailsThumb .innerRightShade {
        display: none !important;
    }
    body.page-video_details .detailsThumb .innerShade {
        display: block !important;
    }
    /* Overlay the title logo on the artwork (editors-pick style): pull the
       content block up so the logo sits on the media bottom, with the Play
       button + actions flowing below it. Mirrors the series hero (same -16vw
       negative margin). The media stays a clean 16:9. */
    body.page-video_details .videoDetails .overlapData {
        margin: -16vw 0 0 !important;
        height: auto !important;
        padding: 0 !important;
    }
    /* Series hero: the overlapData keeps its .insidePadding 16px inset, which
       pushed the logo / Play / action row to x31 while the description below
       sits at x15 — misaligned. Zero just its horizontal padding so the series
       hero shares the same left/right margin as everything else (vertical
       spacing/overlay untouched — series has no -8vw gap issue). */
    body.page-series_details .videoDetails .overlapData {
        padding-inline: 0 !important;
    }
    body.page-video_details .overlapData .row {
        margin: 0 !important;
    }
    body.page-video_details .overlapData [class*="col-"] {
        padding-inline: 0 !important;
        max-width: 100% !important;
        flex: 0 0 100% !important;
    }

    /* The meta row (year · rating · runtime) sat flush against the action-icon
       labels — looked cramped. Give it breathing room above. (Movie only; the
       series hero's negative-margin overlay already leaves a gap here.) */
    body.page-video_details .vxm-meta {
        margin-top: 20px !important;
    }

    /* The lower content section (description / meta / categories / episodes)
       sits in a Bootstrap .row whose col gutter indented it ~16px further right
       than the logo, Play button and action row above — so the text block
       didn't share their left edge. Zero the row margin + col padding so the
       whole details column lines up on one left/right margin (symmetrical). */
    body.page-video_details .videoDetails > .insidePadding,
    body.page-series_details .videoDetails > .insidePadding {
        /* was 0, because the surrounding containers used to supply 15px; they
           are zeroed now, so the gutter has to come from here or the section
           below the CTA runs to the screen edge */
        padding-inline: var(--vxl-gutter, 16px) !important;
    }
    body.page-video_details .videoDetails > .insidePadding > .row,
    body.page-series_details .videoDetails > .insidePadding > .row {
        margin-inline: 0 !important;
    }
    body.page-video_details .videoDetails > .insidePadding > .row > [class*="col-"],
    body.page-series_details .videoDetails > .insidePadding > .row > [class*="col-"] {
        padding-inline: 0 !important;
    }
    /* ...but the "More Like This" grid (col-6 cards) needs its gutter back —
       the rule above made the related cards touch. (The description/meta cols
       are col-12, so they stay flush-aligned to the page margin.) */
    body.page-video_details .videoDetails > .insidePadding > .row:has(> .col-6),
    body.page-series_details .videoDetails > .insidePadding > .row:has(> .col-6) {
        margin-inline: -6px !important;
    }
    body.page-video_details .videoDetails > .insidePadding > .row > .col-6,
    body.page-series_details .videoDetails > .insidePadding > .row > .col-6 {
        padding-inline: 6px !important;
    }
    /* Clean, uniform related cards: drop the boxy dark bg + the ragged
       description / maturity-date line — thumbnail + title reads far cleaner. */
    body.page-video_details .cardBg,
    body.page-series_details .cardBg { background: transparent !important; }
    body.page-video_details .cardBg .vid-description,
    body.page-series_details .cardBg .vid-description,
    body.page-video_details .cardBg p:has(.badge-secondary),
    body.page-series_details .cardBg p:has(.badge-secondary) { display: none !important; }

    /* On mobile this is a page, not an overlay — the X is confusing */
    body.page-video_details .series-detail-close,
    body.page-series_details .series-detail-close {
        display: none;
    }

    /* app-style action stack (reference: Play → secondary → icon row):
       Play primary full-width first, Trailer secondary full-width below,
       then the action circles (watchlist / like / share) in an even row */
    body.page-video_details .detailsIcon,
    body.page-series_details .detailsIcon {
        width: 100%;
        display: flex;
        flex-wrap: wrap;
        align-items: flex-start;
        justify-content: space-between;
        gap: 12px 10px;
    }

    body.page-video_details .detailsIcon .playResumeBtn:not([data-stats-event="trailer_play"]),
    body.page-series_details .detailsIcon .playResumeBtn:not([data-stats-event="trailer_play"]) {
        order: 1;
        flex: 1 1 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 8px; /* space between the play glyph and the "Play"/"Resume" label */
        height: 48px;
        /* 8, not the home hero's 6 — the detail CTA is a taller, softer button
           in the app, and the two live in different scopes so they can differ. */
        border-radius: 8px;
        font-size: 15px;
        font-weight: 700;
        margin-right: 0 !important;
    }

    /* ---- Prime-style labeled action row (26.158.4) ----------------------
       Below the primary Play button the actions become labeled icon columns:
       Trailer · My List · Like · Not for me · Share. Desktop is untouched —
       .vxm-actCol is display:contents there and .vxm-actLabel is hidden (see
       the component <style> defaults); these rules only restructure the row
       on mobile. */

    /* each labeled column: a circle on top, a caption underneath.
       .vxr__optBtn is the like/dislike button once the reveal is unfolded. */
    body.page-video_details .detailsIcon .vxm-actCol,
    body.page-series_details .detailsIcon .vxm-actCol,
    body.page-video_details .detailsIcon .vxr__optBtn,
    body.page-series_details .detailsIcon .vxr__optBtn {
        order: 4;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 7px;
        width: auto;
        height: auto;
        min-width: 0;
        margin: 0 !important;
        padding: 0 !important;
        background: none !important;
        border: 0 !important;
    }
    body.page-video_details .detailsIcon .vxm-actCol--list,
    body.page-series_details .detailsIcon .vxm-actCol--list { order: 3; }
    body.page-video_details .detailsIcon .vxm-actCol--share,
    body.page-series_details .detailsIcon .vxm-actCol--share { order: 6; }

    /* Prime-style: plain, bright, borderless icons (no ring / no fill), a
       larger glyph in pure white. The `.rundedBtn` base pins a 42px max + 2px
       border + grey fill — all reset here so the glyph stands on its own. */
    body.page-video_details .detailsIcon .vxm-actCol > .rundedBtn,
    body.page-video_details .detailsIcon .vxm-actCol > .boxBtn,
    body.page-series_details .detailsIcon .vxm-actCol > .rundedBtn,
    body.page-series_details .detailsIcon .vxm-actCol > .boxBtn,
    body.page-video_details .detailsIcon .vxr__optBtn > i,
    body.page-series_details .detailsIcon .vxr__optBtn > i,
    body.page-video_details .detailsIcon .vxr__optBtn > .vxm-ico,
    body.page-series_details .detailsIcon .vxr__optBtn > .vxm-ico {
        box-sizing: border-box !important;
        width: auto !important;
        /* match the 24px glyph so the My List / Share buttons are the same
           height as the trailer / like / dislike icon cells — otherwise a 34px
           box pushed their labels ~10px lower than the rest of the row. */
        height: 24px !important;
        min-width: 24px !important;
        min-height: 24px !important;
        max-width: none !important;
        max-height: none !important;
        flex: 0 0 auto !important;
        padding: 0 !important;
        border: 0 !important;
        border-radius: 0 !important;
        background: transparent !important;
        box-shadow: none !important;
        display: flex;
        align-items: center;
        justify-content: center;
        margin: 0 !important;
        color: #fff !important;
        font-size: 24px !important;
    }
    /* the button-based icons (My List / Share) carry the glyph in an inner
       <i> — size + brighten it (base `.rundedBtn i` pins 1.5rem). */
    body.page-video_details .detailsIcon .vxm-actCol > .rundedBtn i,
    body.page-video_details .detailsIcon .vxm-actCol > .boxBtn i,
    body.page-series_details .detailsIcon .vxm-actCol > .rundedBtn i,
    body.page-series_details .detailsIcon .vxm-actCol > .boxBtn i,
    body.page-video_details .detailsIcon .vxm-actCol > .rundedBtn .vxm-ico,
    body.page-video_details .detailsIcon .vxm-actCol > .boxBtn .vxm-ico,
    body.page-series_details .detailsIcon .vxm-actCol > .rundedBtn .vxm-ico,
    body.page-series_details .detailsIcon .vxm-actCol > .boxBtn .vxm-ico {
        /* fixed 24px — `width:auto` on an SVG is unreliable (it collapsed the
           nested Share glyph to 0); a fixed box is also smaller + uniform. */
        width: 24px !important;
        height: 24px !important;
        min-width: 0 !important;
        max-width: none !important;
        font-size: 24px !important;
        line-height: 1 !important;
        color: #fff !important;
    }

    /* Unified action-row glyph size — a fixed 24px box for EVERY icon in the
       row (My List, Share, Like, Not-for-me, Trailer). `width:auto` on an SVG
       is unreliable and collapsed the Share glyph (nested in a .fill-height
       div) to 0×0; a fixed box also makes the icons a touch smaller + uniform.
       Clears the 34px min/max box constraints from the rules above. */
    body.page-video_details .detailsIcon .vxm-actCol .vxm-ico,
    body.page-series_details .detailsIcon .vxm-actCol .vxm-ico,
    body.page-video_details .detailsIcon .vxr__optBtn .vxm-ico,
    body.page-series_details .detailsIcon .vxr__optBtn .vxm-ico,
    body.page-video_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] .vxm-ico,
    body.page-series_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] .vxm-ico {
        width: 24px !important;
        height: 24px !important;
        min-width: 0 !important;
        min-height: 0 !important;
        max-width: none !important;
        max-height: none !important;
        font-size: 24px !important;
        color: #fff !important;
    }

    /* captions — bright and crisp (Prime-style), not the old dim grey */
    body.page-video_details .detailsIcon .vxm-actLabel,
    body.page-series_details .detailsIcon .vxm-actLabel {
        display: block;
        font-size: 11px;
        line-height: 1.2;
        font-weight: 500;
        color: rgba(255, 255, 255, 0.92);
        text-align: center;
        white-space: nowrap;
    }
    /* the like/dislike buttons carry a hover-tooltip ::after bubble by default
       (position:absolute; opacity:0). On mobile repurpose it as the static
       caption under the icon, and drop the tooltip caret ::before. */
    body.page-video_details .detailsIcon .vxr__optBtn::after,
    body.page-series_details .detailsIcon .vxr__optBtn::after {
        content: attr(aria-label);
        position: static;
        transform: none;
        opacity: 1;
        padding: 0;
        border-radius: 0;
        background: none;
        color: rgba(255, 255, 255, 0.92);
        font-size: 11px;
        font-weight: 500;
        line-height: 1.2;
        white-space: nowrap;
        pointer-events: none;
    }
    body.page-video_details .detailsIcon .vxr__optBtn::before,
    body.page-series_details .detailsIcon .vxr__optBtn::before { display: none !important; }

    /* Trailer: the pill button becomes a labeled column like the rest */
    body.page-video_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"],
    body.page-series_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] {
        order: 2;
        flex: 0 0 auto;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 7px;
        height: auto;
        width: auto;
        padding: 0 !important;
        margin: 0 !important;
        background: none !important;
        border: 0 !important;
        box-shadow: none !important;
        font-weight: 400;
    }
    body.page-video_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] > i,
    body.page-series_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] > i,
    body.page-video_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] > .vxm-ico,
    body.page-series_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] > .vxm-ico {
        width: auto !important;
        height: 34px;
        border: 0 !important;
        border-radius: 0 !important;
        background: none !important;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 28px !important;
        color: #fff !important;
        margin: 0 !important;
    }
    body.page-video_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] > span,
    body.page-series_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] > span {
        font-size: 11px;
        font-weight: 500;
        color: rgba(255, 255, 255, 0.92);
    }

    /* unfold the like/dislike reveal into two flat labeled columns */
    body.page-video_details .detailsIcon .vxr,
    body.page-series_details .detailsIcon .vxr,
    body.page-video_details .detailsIcon .vxr__pop,
    body.page-series_details .detailsIcon .vxr__pop {
        display: contents;
    }
    body.page-video_details .detailsIcon .vxr__trigger,
    body.page-series_details .detailsIcon .vxr__trigger { display: none !important; }
    /* Prime order is Like (4) then Not-for-me (5); the pop's DOM order is
       dislike, like — so bump like ahead by position (language-agnostic). */
    body.page-video_details .detailsIcon .vxr__pop .vxr__optBtn:nth-child(1),
    body.page-series_details .detailsIcon .vxr__pop .vxr__optBtn:nth-child(1) { order: 5; }
    body.page-video_details .detailsIcon .vxr__pop .vxr__optBtn:nth-child(2),
    body.page-series_details .detailsIcon .vxr__pop .vxr__optBtn:nth-child(2) { order: 4; }

    /* The trailer play/pause circle has no place in the action row — the
       trailer is ambient, and a second play control next to Play is confusing. */
    body.page-video_details .detailsIcon .trailerPlayPauseBtn,
    body.page-series_details .detailsIcon .trailerPlayPauseBtn { display: none !important; }

    /* Mute, however, the app DOES show — as a disc on the artwork's bottom-right
       corner, because an autoplaying trailer you cannot silence is hostile.
       `.detailsThumb` is the positioning ancestor and starts at the top of the
       artwork, whose height is a known 56.25vw now that it is full-bleed 16:9 —
       so the offset from the top is exact rather than guessed. */
    body.page-video_details .detailsIcon .playPausePage,
    body.page-video_details .detailsIcon .playPauseModal,
    body.page-series_details .detailsIcon .iframePlayPauseSeries {
        position: absolute;
        top: calc(56.25vw - 52px);
        inset-inline-end: var(--vxl-gutter, 16px);
        z-index: 4;
        display: flex !important;
        align-items: center;
        justify-content: center;
        width: 36px !important;
        min-width: 36px !important;
        height: 36px !important;
        min-height: 36px !important;
        margin: 0 !important;
        padding: 0 !important;
        border: 0 !important;
        border-radius: 50% !important;
        background: color-mix(in srgb, var(--vxl-scrim-solid) 55%, transparent) !important;
        -webkit-backdrop-filter: blur(8px);
        backdrop-filter: blur(8px);
        color: var(--vxl-ink) !important;
    }
    body.page-video_details .detailsIcon .playPausePage .vxm-ico,
    body.page-video_details .detailsIcon .playPauseModal .vxm-ico,
    body.page-series_details .detailsIcon .iframePlayPauseSeries .vxm-ico {
        width: 18px !important;
        height: 18px !important;
        color: var(--vxl-ink) !important;
    }

    /* ======================================================================
       Content info screen — parity with the native app
       ----------------------------------------------------------------------
       The app's content screen reads, top to bottom:

         back ▸ video ▸ title ▸ genres ▸ meta ▸ [progress] ▸ CTA ▸ description
         ▸ cast ▸ action icons ▸ tab ▸ content

       The web page has all of those pieces but not in that order, and half of
       them lived in a hero overlay stacked ON the artwork. The template adds
       the inert text (title / genres / meta / description / cast) for phones
       only — `v-if="isMobile"`, so exactly one copy exists — and the rules
       below re-order the interactive parts around it and hide the desktop
       duplicates. Nothing here is coloured: every value comes from the
       tenant's own palette via the vxl tokens.
       ====================================================================== */

    /* No top bar. The app's content screen is chromeless — the artwork starts
       at the status bar. Everything the header offered (Home, Search, My List,
       More) is in the bottom tab bar, and the back control below replaces the
       only thing that isn't. */
    body.page-video_details .vxh,
    body.page-series_details .vxh {
        display: none !important;
    }
    body.page-video_details,
    body.page-series_details {
        padding-top: 0 !important;
    }
    /* `.contentHolder.pt-5.mt-5` reserved 102px for the header that is no
       longer there, so the artwork started a third of the way down the screen. */
    body.page-video_details .contentHolder,
    body.page-series_details .contentHolder {
        margin-top: 0 !important;
        padding-top: 0 !important;
    }
    /* The page sits in two nested Bootstrap `.container`s, each contributing
       15px. Zero both and let --vxl-gutter be the single source of the inset,
       exactly as on the home screen — otherwise the text indents to 31px while
       the app has it at 16. */
    body.page-video_details .vxm-page,
    body.page-series_details .vxm-page,
    body.page-video_details .detailsContainer,
    body.page-series_details .detailsContainer {
        padding-inline: 0 !important;
    }

    /* Back control — a 36px glass disc over the artwork with a 44px hit area,
       inset on the page gutter and clear of the status bar / notch. */
    .vxm-back {
        position: absolute;
        top: max(12px, env(safe-area-inset-top));
        inset-inline-start: var(--vxl-gutter, 16px);
        z-index: 4;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 36px;
        height: 36px;
        padding: 0;
        border: 0;
        border-radius: 50%;
        background: color-mix(in srgb, var(--vxl-scrim-solid) 55%, transparent);
        -webkit-backdrop-filter: blur(8px);
        backdrop-filter: blur(8px);
        color: var(--vxl-ink);
    }
    .vxm-back::after {
        /* 44px target without a 44px disc — see the pill treatment on home */
        content: "";
        position: absolute;
        inset: -4px;
    }
    .vxm-back .vxm-ico {
        width: 20px !important;
        height: 20px !important;
    }

    /* Artwork is full-bleed on this screen, unlike the home hero's inset card:
       the app runs the video edge to edge and starts the text below it. That
       also means the old overlay pull-up has to go, or the title sits on the
       picture. */
    body.page-video_details .detailsThumb,
    body.page-series_details .detailsThumb {
        /* full-bleed regardless of what padding any ancestor container adds —
           safer here than unwinding a specific 15px, which two different
           containers were each contributing */
        width: 100vw;
        margin-inline: calc(50% - 50vw);
    }
    body.page-video_details .detailsThumb .ratio-16x9 {
        border-radius: 0;
    }
    /* Series as well as movies: the hero overlay pulled the text up onto the
       artwork, which is right when a title logo is the only thing overlaid and
       wrong now that the whole info head sits there — the title and genre line
       were printing over the picture. */
    body.page-video_details .videoDetails .overlapData,
    body.page-series_details .videoDetails .overlapData {
        margin-top: 0 !important;
    }
    body.page-video_details .detailsThumb,
    body.page-series_details .detailsThumb {
        height: auto !important;
    }
    body.page-series_details .detailsThumb .ratio-16x9 {
        height: auto !important;
        aspect-ratio: 16 / 9;
        padding-top: 0 !important;
        border-radius: 0;
        overflow: hidden;
    }
    body.page-video_details .detailsThumb .innerShade,
    body.page-series_details .detailsThumb .innerShade {
        /* nothing overlays the artwork any more, so the scrim has nothing to
           protect and only darkens the picture */
        display: none !important;
    }
    /* The overlay title/logo is replaced by the real <h1> in the info head. */
    body.page-video_details .vxm-heroCol .videoLogo,
    body.page-series_details .vxm-heroCol .videoLogo {
        display: none !important;
    }
    /* Page gutter for the whole text column. */
    body.page-video_details .videoDetails .overlapData,
    body.page-series_details .videoDetails .overlapData {
        padding-inline: var(--vxl-gutter, 16px) !important;
    }

    /* ---- info head: title, genres, meta ------------------------------- */
    /* Vertical rhythm below is measured off the app's content screen and
       scaled to a 375pt viewport. Everything was 5-25px tighter than the app
       before, which read as cramped rather than wrong — worth pinning rather
       than eyeballing. */
    .vxm-info__head {
        margin-top: 17px;
    }
    .vxm-info__title {
        margin: 0;
        font-size: 20px;
        font-weight: 700;
        line-height: 1.25;
        letter-spacing: -0.01em;
        color: var(--vxl-ink);
        text-wrap: balance;
    }
    .vxm-info__genres {
        margin: 10px 0 0;
        font-size: 13px;
        line-height: 1.35;
        color: var(--vxl-ink-muted);
    }
    .vxm-info__meta {
        display: flex;
        align-items: center;
        flex-wrap: wrap;
        gap: 8px;
        margin-top: 14px;
        font-size: 13px;
        color: var(--vxl-ink-muted);
    }
    .vxm-info__badge {
        padding: 2px 6px;
        border-radius: var(--vxl-radius-sm, 4px);
        background: var(--vxl-surface-2, rgba(255, 255, 255, 0.12));
        color: var(--vxl-ink);
        font-size: 12px;
        line-height: 1.4;
    }
    .vxm-info__badge--live {
        background: var(--vod-primary, #e50914);
        color: var(--vod-btn-primary-text, #fff);
        font-weight: 700;
    }

    /* ---- progress + "53m remaining" ----------------------------------- */
    body.page-video_details .overlapData .row.vxm-info__progressRow,
    body.page-series_details .overlapData .row.vxm-info__progressRow {
        display: flex;
        align-items: center;
        /* Bootstrap's `.row` wraps, which put "10m remaining" on a line of its
           own under a full-width bar. The app has them side by side. */
        flex-wrap: nowrap !important;
        gap: 12px;
        /* !important throughout this rule and the next: the generic
           `.overlapData .row` / `.overlapData [class*="col-"]` resets above
           already carry !important, so anything less specific here loses. */
        margin: 26px 0 0 !important;
    }
    body.page-video_details .vxm-info__progressRow > [class*="col-"],
    body.page-series_details .vxm-info__progressRow > [class*="col-"] {
        /* basis 0, not auto: with `auto` the column sizes to its content — a
           100%-wide bar — and pushed the label clean off the right of the
           screen. Zero basis lets the two share the row. */
        flex: 1 1 0 !important;
        min-width: 0;
        max-width: none !important;
        padding: 0 !important;
    }
    /* The track ships as Bootstrap's near-white `#e9ecef`, which on a dark page
       reads as a solid white rule rather than as the unwatched remainder of the
       bar. Derive it from the tenant's own surface so it recedes on any palette. */
    body.page-video_details .vxm-info__progressRow .progress,
    body.page-series_details .vxm-info__progressRow .progress {
        height: 3px !important;
        border-radius: var(--vxl-radius-pill, 999px);
        background: var(--vxl-surface-2, rgba(255, 255, 255, 0.16)) !important;
        overflow: hidden;
    }
    .vxm-info__remaining {
        flex: 0 0 auto;
        font-size: 12px;
        color: var(--vxl-ink-muted);
        white-space: nowrap;
    }

    /* ---- CTA ----------------------------------------------------------
       One button is the tenant's accent. TWO buttons means the title is part
       watched, and then the app inverts the primary — white on black. Inverting
       against the tenant's own ink/surface keeps that on a light palette too,
       where a literal white button would vanish. */
    body.page-video_details .detailsIcon:has(.vxm-info__cta--secondary) .playResumeBtn[data-stats-event="continue_watching_resume"],
    body.page-series_details .detailsIcon:has(.vxm-info__cta--secondary) .playResumeBtn[data-stats-event="continue_watching_resume"] {
        background: var(--vxl-ink) !important;
        border-color: var(--vxl-ink) !important;
        color: var(--vod-body-bg, #000) !important;
    }
    body.page-video_details .detailsIcon .vxm-info__cta--secondary,
    body.page-series_details .detailsIcon .vxm-info__cta--secondary {
        background: var(--vod-ep-secondary-button-bg, rgba(109, 109, 110, 0.7)) !important;
        border-color: transparent !important;
        color: var(--vod-ep-secondary-button-text, #fff) !important;
    }

    /* ---- description + cast, seated between the CTA and the icon row --- */
    body.page-video_details .detailsIcon .vxm-info__body,
    body.page-series_details .detailsIcon .vxm-info__body {
        order: 3;
        flex: 1 1 100%;
    }
    .vxm-info__descWrap {
        position: relative;
    }
    .vxm-info__desc {
        margin: 0;
        font-size: 14px;
        line-height: 1.5;
        color: var(--vxl-ink);
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }
    .vxm-info__descWrap.is-open .vxm-info__desc {
        display: block;
        overflow: visible;
    }
    /* "Show more" sits at the end of the clamped second line, as the app has
       it — hence absolute, with a short fade so it never lands mid-word. When
       expanded it drops onto its own line instead of covering the last one. */
    .vxm-info__toggle {
        position: absolute;
        inset-block-end: 0;
        inset-inline-end: 0;
        padding: 0 0 0 14px;
        border: 0;
        background: linear-gradient(
            to right,
            transparent 0,
            var(--vod-body-bg, #000) 12px,
            var(--vod-body-bg, #000) 100%);
        color: var(--vxl-ink);
        font-size: 14px;
        font-weight: 600;
        line-height: 1.5;
    }
    /* The clamp cuts wherever the line ends, which is usually mid-word — the
       app shows an ellipsis there. Render it as part of the control so it can
       never drift away from the text it belongs to. */
    .vxm-info__toggle::before {
        content: "… ";
        color: var(--vxl-ink-muted);
        font-weight: 400;
    }
    .vxm-info__descWrap.is-open .vxm-info__toggle::before {
        content: none;
    }
    .vxm-info__descWrap.is-open .vxm-info__toggle {
        position: static;
        display: block;
        margin-top: 4px;
        padding: 0;
        background: none;
    }
    .vxm-info__cast {
        margin: 8px 0 0;
        font-size: 13px;
        line-height: 1.4;
        color: var(--vxl-ink-muted);
    }

    /* Rhythm around the CTA. `.detailsIcon`'s own 18px row-gap covers the space
       BELOW the button but nothing above it, so the meta line sat almost on top
       of it. The description needs the same air before the icon row. */
    body.page-video_details .detailsIcon,
    body.page-series_details .detailsIcon {
        margin-top: 26px;
    }
    body.page-video_details .detailsIcon .vxm-info__body,
    body.page-series_details .detailsIcon .vxm-info__body {
        /* the row's own 12px gap stacks on top of these, giving the app's 26
           above the description and 24 below it */
        margin-block: 14px 12px;
    }

    /* The "About <title>" block below repeats the title, categories and
       maturity blurb that the info head already carries — the app's screen ends
       at the related grid. */
    body.page-video_details .videoDetails > .insidePadding > h2.h4,
    body.page-series_details .videoDetails > .insidePadding > h2.h4 {
        display: none !important;
    }
    body.page-video_details .videoDetails > .insidePadding > h2.h4 ~ *,
    body.page-series_details .videoDetails > .insidePadding > h2.h4 ~ * {
        display: none !important;
    }

    /* The icon row moves below the description. Its columns were ordered 2-6;
       everything shifts past the description's `order: 3`. */
    body.page-video_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"],
    body.page-series_details .detailsIcon .playResumeBtn[data-stats-event="trailer_play"] { order: 4; }
    body.page-video_details .detailsIcon .vxm-actCol--list,
    body.page-series_details .detailsIcon .vxm-actCol--list { order: 5; }
    body.page-video_details .detailsIcon .vxm-actCol,
    body.page-series_details .detailsIcon .vxm-actCol,
    body.page-video_details .detailsIcon .vxr__optBtn,
    body.page-series_details .detailsIcon .vxr__optBtn { order: 6; }
    body.page-video_details .detailsIcon .vxr__pop .vxr__optBtn:nth-child(1),
    body.page-series_details .detailsIcon .vxr__pop .vxr__optBtn:nth-child(1) { order: 7; }
    body.page-video_details .detailsIcon .vxr__pop .vxr__optBtn:nth-child(2),
    body.page-series_details .detailsIcon .vxr__pop .vxr__optBtn:nth-child(2) { order: 6; }
    body.page-video_details .detailsIcon .vxm-actCol--share,
    body.page-series_details .detailsIcon .vxm-actCol--share { order: 8; }

    /* ---- the desktop copies of what the info head now shows ------------ */
    body.page-video_details .videoDetails > .insidePadding .vxm-meta,
    body.page-series_details .videoDetails > .insidePadding .vxm-meta,
    body.page-video_details .videoDetails > .insidePadding > .row:first-child > .col-12 > p,
    body.page-series_details .videoDetails > .insidePadding > .row:first-child > .col-12 > p {
        display: none !important;
    }

    /* ---- Episodes: the app's tab + season pill ------------------------
       The app heads this section with a tab-styled label carrying a red rule,
       and a compact dark pill for the season. The web page shipped an <h4> and
       a full-width bordered <select>, which read as a settings form. Restyled,
       not rebuilt — the <select> keeps native behaviour (and its accessibility)
       and only loses the chrome. */
    body.page-series_details .vxm-epList > .col:first-child {
        flex: 0 0 auto;
        width: auto;
        padding-inline: 0 !important;
    }
    body.page-series_details .vxm-epList > .col:first-child > h2 {
        margin: 0 0 12px !important;
        padding-bottom: 8px;
        border-bottom: 2px solid var(--vxl-accent, var(--vod-primary, #e50914));
        font-size: 15px !important;
        font-weight: 700 !important;
        color: var(--vxl-ink);
    }
    body.page-series_details .vxm-epList > .col:nth-child(2) {
        flex: 0 0 auto;
        width: auto;
        margin-inline-start: auto;
        padding-inline: 0 !important;
    }
    body.page-series_details .vxm-epList > .col:nth-child(2) > .mb-4 {
        margin-bottom: 12px !important;
    }
    body.page-series_details .vxm-epList select.form-control {
        width: auto;
        height: 32px;
        min-height: 32px;
        padding: 0 30px 0 12px;
        border: 0;
        border-radius: var(--vxl-radius-pill, 999px);
        background-color: var(--vxl-surface-2, rgba(255, 255, 255, 0.12));
        color: var(--vxl-ink);
        font-size: 13px;
        font-weight: 600;
    }

    /* Episode rows — thumbnail, then title / runtime / synopsis, split by a
       hairline. The stock row leads with a sequence number in its own Bootstrap
       column, which the app does not have and which cost ~8% of the width on a
       phone; the number is already the first thing in the episode title. */
    body.page-series_details .vxm-ep .col-1 {
        display: none !important;
    }
    body.page-series_details .vxm-ep .col-10 {
        flex: 0 0 100% !important;
        max-width: 100% !important;
        padding-inline: 0 !important;
    }
    /* The episode's inner Bootstrap `.row` carries the usual -15px gutter, and
       with the columns' own padding zeroed above there was nothing left to
       cancel it — the thumbnail hung a pixel off the left edge of the screen
       while every other block on the page started at 16. */
    body.page-series_details .vxm-ep > .row {
        margin-inline: 0 !important;
    }
    /* Thumbnail: the app's is ~100px, a quarter of the row, leaving the title
       and synopsis room to breathe. Unconstrained it took 40% and squeezed the
       text into a narrow ragged column. */
    body.page-series_details .vxm-ep .listMenu > a.img {
        flex: 0 0 108px;
        width: 108px;
        margin-bottom: 0 !important;
        border-radius: var(--vxl-radius-sm, 4px);
        overflow: hidden;
    }
    body.page-series_details .vxm-ep .listMenu {
        display: flex;
        align-items: flex-start;
        gap: 12px;
    }
    body.page-series_details .vxm-ep .listMenu > div:last-child {
        flex: 1 1 auto;
        min-width: 0;
        padding: 0 !important;
    }
    body.page-series_details .vxm-ep hr {
        margin: 14px 0;
        border-color: var(--vxl-line);
    }

    /* ---- Related grid: three-up posters -------------------------------
       The app ends the screen with a three-column poster wall, not the two-up
       landscape stills the page ships. The card takes a `portrait` prop for
       this (it also picks the poster artwork); the columns are Bootstrap
       `col-6`, so the third column has to come from here. */
    body.page-video_details .videoDetails > .insidePadding > .row > .col-6,
    body.page-series_details .videoDetails > .insidePadding > .row > .col-6 {
        flex: 0 0 33.3333% !important;
        max-width: 33.3333% !important;
    }
    body.page-video_details .videoDetails > .insidePadding .cardBg .ratio.portrait,
    body.page-series_details .videoDetails > .insidePadding .cardBg .ratio.portrait {
        border-radius: var(--vxl-radius-sm, 4px);
        overflow: hidden;
    }
    /* A poster carries its own title, so the caption below it is noise — and at
       a third of the width it truncated to "Ghost Protocol:…" anyway.

       The series page needs this MORE than the movie page, not less: its
       related cards carry a title AND a synopsis, and at a third of the width
       those wrapped to one or two words per line and ran for a dozen lines
       each. Scoping the three-up columns to both pages while scoping this to
       one was the bug. */
    body.page-video_details .videoDetails > .insidePadding .cardBg .card-img-overlay,
    body.page-series_details .videoDetails > .insidePadding .cardBg .card-img-overlay,
    body.page-video_details .videoDetails > .insidePadding .cardBg > a ~ *,
    body.page-series_details .videoDetails > .insidePadding .cardBg > a ~ * {
        display: none !important;
    }

    /* ---- "More Like This" as the app's tab ----------------------------- */
    body.page-video_details .videoDetails > .insidePadding > h2.h5,
    body.page-series_details .videoDetails > .insidePadding > h2.h5 {
        display: inline-block;
        margin: 11px 0 16px !important;
        padding-bottom: 8px;
        border-bottom: 2px solid var(--vxl-accent, var(--vod-primary, #e50914));
        font-size: 15px;
        font-weight: 700;
        color: var(--vxl-ink);
    }

    /* Live TV (live_v2) keeps the ORIGINAL responsive layout — only the
       bottom tab bar is added (it's out of the immersive-hide list below).
       The tab bar's body padding-bottom keeps the channel list clear. */

    /* ---- immersive pages: no tab bar ---------------------- */
    /* Live TV (live_v2) is a browse screen (channel list), reached from the
       Live TV pill — it keeps the tab bar. Only true fullscreen playback and
       the standalone EPG guide hide it. */
    body.page-watch_video .vxm-tabbar,
    body.page-live_details .vxm-tabbar,
    body.page-epg .vxm-tabbar {
        display: none;
    }

    /* immersive pages hide the bar → no clearance needed (mirrors the
       .contentHolder tab-bar clearance above) */
    body.page-watch_video .contentHolder,
    body.page-live_details .contentHolder,
    body.page-epg .contentHolder {
        padding-bottom: 0 !important;
    }

    /* ---- Watch page — app-style portrait player -------------- */
    /* NB: the watch page renders with a blank-layout body (no page-watch_video
       class) — scope on .watchVideoPage (#wrapper) instead. Full-bleed 16:9
       player at the top, clear of the fixed header; title + actions below. */
    /* The site header does not belong on a playback screen. Every layout: the
       player is the page, and the player supplies its own 44px back control
       (`.vdx-back-btn`, which is why its wrapper carries `vdx-has-back`) — so
       hiding the header costs no route out, and the tab bar is already hidden
       here. This applies in portrait too, not only the landscape fullscreen
       rule at the end of this file. */
    .watchVideoPage .vxh {
        display: none !important;
    }

    .watchVideoPage .watchVideoInner {
        padding-inline: 0 !important;
        padding-top: 0 !important;
    }
    /* the player sits in #playerApp > .row > .col-12 — kill the Bootstrap
       gutters so the 16:9 player is edge-to-edge */
    .watchVideoPage #playerApp,
    .watchVideoPage #playerApp .row,
    .watchVideoPage #playerApp [class*="col-"] {
        padding-inline: 0 !important;
        margin-inline: 0 !important;
    }
    .watchVideoPage .player-ratio {
        border-radius: 0;
    }
    /* title / rating / comments: keep readable side padding */
    .watchVideoPage .video-title-container,
    .watchVideoPage #commentsDiv,
    .watchVideoPage #watchVideo > .clearfix {
        padding-inline: 16px;
    }
    /* title row: wrap cleanly instead of colliding the actions + view count */
    .watchVideoPage .video-title-container {
        flex-wrap: wrap;
        gap: 8px;
        padding-top: 14px !important;
    }
    .watchVideoPage .video-title-container h1 {
        flex: 1 1 100%;
        margin-right: 0 !important;
    }

    /* ---- top spacing under the fixed header + pills ---------- */
    /* body.html wraps content in .contentHolder.pt-5.mt-5. On home
       the hero card carries its own top margin, so the holder needs
       none; the movies/tv-shows homes start with the heading + rails
       and must clear the fixed header + pills row instead. */
    body.page-home .contentHolder.pt-5 {
        padding-top: 0 !important;
    }

    /* home without an editors pick (body.notEditorPick) needs the
       cushion too — nothing else provides it */
    body.page-home.notEditorPick .contentHolder.pt-5,
    body.page-movies .contentHolder.pt-5,
    body.page-tv_shows .contentHolder.pt-5,
    body.page-video_categories .contentHolder.pt-5,
    body.page-series_categories .contentHolder.pt-5,
    body.page-all-movies .contentHolder.pt-5,
    body.page-all-tv-shows .contentHolder.pt-5 {
        padding-top: calc(var(--vxm-header-h) + 58px) !important;
    }

    /* group pages: when a hero card is present (#new-editorpick) it brings
       its own top margin (like home) — zero the holder; when there's no
       hero, the rails need the header+pills cushion instead */
    body.page-content_list_group .contentHolder.pt-5:has(#new-editorpick) {
        padding-top: 0 !important;
    }
    body.page-content_list_group .contentHolder.mt-5:has(#new-editorpick) {
        margin-top: 0 !important;
    }
    body.page-content_list_group .contentHolder.pt-5:not(:has(#new-editorpick)) {
        padding-top: calc(var(--vxm-header-h) + 58px) !important;
    }

    /* More screen: the header is fixed, so clear it — but the desktop
       pt-5 + mt-5 stacked a huge gap above the "Profile & More" title */
    body.page-more .contentHolder.pt-5 {
        padding-top: 0 !important;
    }
    body.page-more .contentHolder.mt-5 {
        margin-top: 0 !important;
    }
    body.page-more .vxm-more {
        padding-top: calc(var(--vxm-header-h) + 12px);
    }

    /* ---- header logo: bigger + firmly left on the app shell ---------- */
    /* the app pages hide search/lang/user, so the logo has room to breathe */
    .vxh__logo {
        margin-inline-start: 4px;
    }
    .vxh__logo img {
        height: 30px !important;
        max-width: 150px !important;
        width: auto;
    }

    body.page-home .contentHolder.mt-5,
    body.page-movies .contentHolder.mt-5,
    body.page-tv_shows .contentHolder.mt-5 {
        margin-top: 0 !important;
    }

    /* ---- Off-screen rails cost nothing to keep on the page ----------------
       Even with rails lazily fetched, a browse page still ends up holding
       hundreds of card elements. Every frame the browser scrolls, it walks
       that whole tree for layout and paint — which a flagship absorbs and a
       low-end Android renders as jitter.

       content-visibility:auto lets it skip layout AND paint for any rail that
       isn't near the viewport, then render it normally as it comes into view.
       contain-intrinsic-size reserves a plausible height so the scrollbar and
       page height stay stable; the `auto` keyword makes the browser remember
       each rail's real height once it has been rendered, so scroll position
       stays honest on the way back up.

       Mobile only, deliberately: this implies paint containment, which would
       clip the desktop hover-expansion (the card scales 1.35 beyond the rail's
       bounds). Desktop keeps its current behaviour. */
    .swiperHolder {
        content-visibility: auto;
        contain-intrinsic-size: auto 280px;
    }

    /* ---- Rail headings — app-style hierarchy -------------------------------
       The title should carry the row and the "View All" affordance should
       defer to it. It didn't: the anchor's `.whiteColor` forced the label to
       full white, and the chevron ships a Bootstrap `.h5` class which renders
       a 20px glyph (`.vxm-ico` is 1em) next to ~16px text — so the pair read
       louder than the title. Mute + right-size the affordance, tighten the
       title. Desktop keeps its larger clamp()-ed title from cards.css. */
    .swiperHolder .headingMain {
        margin-bottom: 10px !important;
    }
    .swiperHolder .swiperTitle {
        font-size: 18px !important;
        /* the markup ships Bootstrap's `.font-weight-normal`, which is
           `font-weight:400 !important` — match it to win the cascade */
        font-weight: 700 !important;
        letter-spacing: -0.01em;
        line-height: 1.25;
    }
    /* A colour rule on the child beats the parent's inherited `.whiteColor`:
       !important there only wins the parent's own cascade, not the child's. */
    .swiperHolder .exploreDiv {
        gap: 1px;
        color: #9b9ba3;
        font-size: 13px;
        font-weight: 600;
        white-space: nowrap;
        /* taller tap target, no visual shift */
        padding: 6px 0 6px 10px;
        margin: -6px 0;
    }
    .swiperHolder .exploreDiv .vxm-ico {
        width: 15px;
        height: 15px;
        stroke-width: 2.5;
    }

    /* ============================================================
       Profiles gate (/profiles) + Edit profile (/profiles/{id}/edit)
       — native-app redesign. The desktop markup scales its fonts with
       vw (≈9px on a phone) and boxes everything in a raised card; these
       rules rebuild both as clean, app-style screens. All scoped to the
       page body class, so desktop is untouched.
       ============================================================ */

    /* ---- "Who's watching?" gate ------------------------------- */
    /* header is fixed here (this page has no tab bar) — clear it and drop
       the desktop contentHolder pt-5/mt-5 stack */
    body.page-profiles .contentHolder.pt-5 { padding-top: 0 !important; }
    body.page-profiles .contentHolder.mt-5 { margin-top: 0 !important; }

    /* stop vertically-centering the column (it buried the heading under
       the fixed header) — top-align and clear the header instead */
    body.page-profiles #profilesHolder .vh100 {
        height: auto !important;
        min-height: calc(100vh - var(--vxm-header-h)) !important;
        justify-content: flex-start !important;
        padding: calc(var(--vxm-header-h) + 24px) 16px 40px;
    }

    body.page-profiles .profileHeading {
        font-size: 26px !important;
        font-weight: 700;
        color: #fff;
        text-align: center;
        margin: 0 0 28px !important;
    }

    /* the profile grid: even columns, no vw margins */
    body.page-profiles #profilesHolder .fullWidth { margin-bottom: 26px !important; }
    body.page-profiles #profilesHolder .container { padding-inline: 0; }
    body.page-profiles #profilesHolder .row.justify-content-center {
        margin-inline: -6px !important;
    }
    body.page-profiles #profilesHolder .row.justify-content-center > [class*="col-"] {
        flex: 0 0 33.3333% !important;
        max-width: 150px !important;
        margin: 0 0 6px !important;      /* overrides the inline `margin:0 1vw 0 0` */
        padding: 0 6px 14px !important;
    }

    /* square, rounded, Netflix-style tiles */
    body.page-profiles .profileTopBox { margin-bottom: 10px !important; }
    body.page-profiles .profileBlock {
        border-radius: 14px !important;
        overflow: hidden;
        transition: transform 0.12s ease;
    }
    body.page-profiles .profileBlock:active { transform: scale(0.96); }
    /* neutralize the desktop hover ring+scale (it sticks after a tap on touch) */
    body.page-profiles .profileTopBox:hover,
    body.page-profiles .profileText:hover ~ .profileTopBox {
        border: 0 !important;
        transform: none !important;
    }

    /* the "Add Profile" tile: dashed placeholder + centered plus */
    body.page-profiles .profileBlock[data-target="#addProfile"] {
        background: rgba(255, 255, 255, 0.05) !important;
        border: 2px dashed rgba(255, 255, 255, 0.22) !important;
    }
    body.page-profiles .profileBlock[data-target="#addProfile"] .vxm-ico {
        width: 36px;
        height: 36px;
        color: #cfcfd6;
    }

    body.page-profiles .profileText {
        font-size: 14px !important;
        font-weight: 500;
        color: #cfcfd6;
        margin-bottom: 2px !important;
    }

    body.page-profiles .profileManageBtn {
        font-size: 14px !important;
        letter-spacing: 0.04em !important;
        padding: 12px 32px !important;
        border-radius: 999px !important;
        border: 1px solid rgba(255, 255, 255, 0.3) !important;
        color: #cfcfd6 !important;
        background: transparent !important;
    }

    /* manage-mode edit/delete overlay circles */
    body.page-profiles .editOptions { gap: 10px; border-radius: 14px; }
    body.page-profiles .editOptions .btn {
        width: 40px;
        height: 40px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        padding: 0;
    }

    /* ---- PIN gate (locked profile) — app-style passcode entry ----
       Base (profiles.html) renders transparent, sharp-cornered squares with a
       1px pure-white border and vw-scaled labels — reads cheap on a phone.
       Rebuild it as a native passcode field: rounded filled cells, a caret-
       less "dots only" look, and a bright lift on the active cell. */
    body.page-profiles .profile-pin-prompt-status {
        font-size: 13px !important;
        letter-spacing: 0.02em;
        color: #8a8a93 !important;
    }
    body.page-profiles .profile-pin-prompt-label {
        font-size: 21px !important;
        font-weight: 600;
        line-height: 1.35;
        padding: 0 24px;
        margin: 0.5em 0 1.4em;
    }
    /* Centre the whole cell row. The OTP component renders the cells in a bare
       `display:flex` div (no justify-content) that sits inside a wrapper sized
       by the hidden — but wider — .warning-message, so the cells pack to the
       left and the row reads off-centre. Force full-width + centered at every
       level down to that row. */
    body.page-profiles .profile-pin-prompt .pin-input-container {
        width: 100% !important;
        margin: 0 !important;
        justify-content: center !important;
    }
    body.page-profiles .profile-pin-prompt .pin-input-container > div { width: 100%; }
    /* Centre the cell row. text-align:center on the parent does NOT centre flex
       children — the row is a bare display:flex (justify-content:flex-start), so
       the cells pack left. Precise structural path — works on every browser.
       (Also kills the non-profiles 130px inline offset.) */
    body.page-profiles .profile-pin-prompt .pin-input-container > div > div:not(.warning-message) {
        justify-content: center !important;
        width: 100%;
        margin-left: 0 !important;
    }
    /* Belt: target the cells' flex parent directly, whatever its nesting. Kept
       in its OWN rule so browsers without :has() drop only this one, not the
       structural fallback above. */
    body.page-profiles .profile-pin-prompt .pin-input-container div:has(> .opt-input-holder) {
        justify-content: center !important;
        width: 100%;
        margin-left: 0 !important;
    }
    body.page-profiles .profile-pin-prompt .pin-number-input {
        width: 58px !important;
        height: 66px !important;
        margin: 0 7px !important;      /* even gaps (overrides the base .mr-3) */
        padding: 0 !important;
        font-size: 30px !important;    /* larger password dot */
        line-height: 1 !important;
        text-align: center !important; /* centre the masked digit in the cell */
        border-radius: 16px !important;
        border: 1.5px solid rgba(255, 255, 255, 0.16) !important;
        background: rgba(255, 255, 255, 0.06) !important;
        color: #fff !important;
        caret-color: transparent !important;   /* passcode feel — dots, no caret */
        -webkit-appearance: none;
        appearance: none;
        transition: border-color 0.18s ease, background-color 0.18s ease,
                    box-shadow 0.18s ease, transform 0.14s ease !important;
    }
    /* the cell being typed into: crisp white border + soft ring + slight lift */
    body.page-profiles .profile-pin-prompt .pin-number-input:focus {
        outline: none !important;
        border-color: #ffffff !important;
        background: rgba(255, 255, 255, 0.12) !important;
        box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.10) !important;
        transform: translateY(-2px);
    }

    /* ---- Edit profile screen ---------------------------------- */
    /* header is hidden on this page — trim the desktop pt-5/mt-5 stack */
    body.page-edit_profile .contentHolder.pt-5 { padding-top: 14px !important; }
    body.page-edit_profile .contentHolder.mt-5 { margin-top: 0 !important; }
    body.page-edit_profile .insidePadding { padding-inline: 16px !important; }
    body.page-edit_profile .editProfileContainer { margin-bottom: 28px !important; }

    /* flush the raised card onto the black bg (app look), like details */
    body.page-edit_profile .card {
        background: transparent !important;
        border: 0 !important;
        border-radius: 0 !important;
        box-shadow: none !important;
        padding: 0 !important;
    }
    body.page-edit_profile .card > h2.h4 {
        font-size: 20px;
        font-weight: 700 !important;
        color: #fff;
        display: flex;
        align-items: center;
        gap: 10px;
        margin-bottom: 16px;
    }
    body.page-edit_profile h2.h4 {
        font-size: 17px !important;
        font-weight: 600 !important;
        color: #fff;
    }
    body.page-edit_profile .backIcon {
        flex: 0 0 auto;
        height: 40px;
        width: 40px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        border-radius: 50% !important;
        border-color: rgba(255, 255, 255, 0.3) !important;
    }

    /* stack the avatar preview above the form (side-by-side is cramped) */
    body.page-edit_profile .editProfileContainer .row > .col-4 {
        flex: 0 0 100% !important;
        max-width: 100% !important;
        display: flex;
        justify-content: center;
        margin-bottom: 18px;
    }
    body.page-edit_profile .editProfileContainer .row > .col-4 .topBox { width: 120px; }
    body.page-edit_profile .editProfileContainer .row > .col-8 {
        flex: 0 0 100% !important;
        max-width: 100% !important;
    }
    body.page-edit_profile .profileBlock { border-radius: 16px !important; overflow: hidden; }

    /* filled, app-style form fields */
    body.page-edit_profile .form-control {
        height: 52px;
        border: 0 !important;
        border-radius: 12px !important;
        background: rgba(255, 255, 255, 0.08) !important;
        color: #fff !important;
        font-size: 16px !important;   /* iOS: 16px+ prevents focus zoom */
        padding: 0 16px !important;
    }

    /* action buttons: bigger tap targets, app-style */
    body.page-edit_profile .clearfix.text-right {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
    }
    body.page-edit_profile .clearfix.text-right .btn {
        flex: 1 1 auto;
        min-height: 46px;
        border-radius: 12px !important;
        font-weight: 600;
        font-size: 14px;
    }

    /* custom-avatar upload row — the native file input (.input-file) has no
       styling of its own, so its default "Choose File" chrome was showing
       through the (now translucent) overlay label. Visually hide the native
       input and turn the label into the whole tap target (its `for=` still
       opens the picker). */
    body.page-edit_profile .custom-file {
        position: relative;
        height: auto;
        display: block;
    }
    body.page-edit_profile .custom-file .input-file {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: 0;
        opacity: 0;
        overflow: hidden;
        pointer-events: none;
    }
    body.page-edit_profile .custom-file-input-label {
        position: static !important;
        height: auto !important;
        min-height: 52px;
        display: flex;
        align-items: center;
        justify-content: center;
        text-align: center;
        border-radius: 12px;
        border: 1px dashed rgba(255, 255, 255, 0.2);
        background: rgba(255, 255, 255, 0.05) !important;
        color: #cfcfd6 !important;
        cursor: pointer;
    }
    body.page-edit_profile .dropbox { margin-top: 10px; }
    body.page-edit_profile .dropbox .btn {
        width: 100%;
        min-height: 46px;
        border-radius: 12px !important;
    }

    /* avatar collection grid → 4 per row (col-6 = 2 is oversized on a phone) */
    body.page-edit_profile .row:has(.imgFull) { margin-inline: -5px !important; }
    body.page-edit_profile .row:has(.imgFull) > [class*="col-"] {
        flex: 0 0 25% !important;
        max-width: 25% !important;
        padding-inline: 5px !important;
    }
    body.page-edit_profile .row:has(.imgFull) .profileBlock { border-radius: 12px !important; }
    body.page-edit_profile .imgFull { border-radius: 12px; }

    /* ---- shared app-style modal box (Add Profile / Set PIN / Remove PIN)
       Scoped by id so the full-screen PIN gate (#staticBackdrop, which
       uses .modal-content.bg-transparent) is never touched. ------------ */
    body.page-profiles #addProfile .modal-dialog,
    body.page-edit_profile #setPin .modal-dialog,
    body.page-edit_profile #removePassword .modal-dialog {
        max-width: 440px;
        margin: 0 auto;
        min-height: calc(100% - 1rem);
        display: flex;
        align-items: center;
        padding: 12px;
    }
    body.page-profiles #addProfile .modal-content,
    body.page-edit_profile #setPin .modal-content,
    body.page-edit_profile #removePassword .modal-content {
        background: #17171c !important;
        border: 0 !important;
        border-radius: 18px !important;
        width: 100%;
        box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
    }
    body.page-profiles #addProfile .modal-header,
    body.page-edit_profile #removePassword .modal-header {
        border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
        padding: 18px 20px;
    }
    body.page-profiles #addProfile .modal-title,
    body.page-edit_profile #removePassword .modal-title {
        font-size: 18px;
        font-weight: 700;
        color: #fff;
    }
    body.page-profiles #addProfile .modal-body,
    body.page-edit_profile #removePassword .modal-body { padding: 20px; }
    body.page-profiles #addProfile .modal-footer,
    body.page-edit_profile #removePassword .modal-footer {
        border-top: 1px solid rgba(255, 255, 255, 0.08) !important;
        padding: 14px 20px 18px;
        gap: 10px;
    }
    body.page-profiles #addProfile .modal-footer > *,
    body.page-edit_profile #removePassword .modal-footer > * { margin: 0 !important; }
    body.page-profiles #addProfile .form-control {
        height: 52px;
        border: 0 !important;
        border-radius: 12px !important;
        background: rgba(255, 255, 255, 0.08) !important;
        color: #fff !important;
        font-size: 16px !important;
        padding: 0 16px !important;
    }
    body.page-profiles #addProfile .modal-footer .btn,
    body.page-edit_profile #removePassword .modal-footer .btn {
        flex: 1 1 0;
        min-height: 48px;
        border-radius: 12px !important;
        font-weight: 700;
        font-size: 15px;
    }
}


/* ==========================================================================
   WATCH — portrait phones present the video ROTATED, filling the screen.

   The OTT convention: start playback on a phone held upright and the video
   takes the whole screen in landscape, so you turn the phone to watch. Native
   apps do it by locking the device orientation. The web cannot: the Screen
   Orientation API's lock() only works from inside fullscreen, fullscreen needs
   a user gesture, and iOS Safari does not implement lock() at all.

   So the player is rotated a quarter turn instead and sized against the swapped
   axes — width is the viewport's height and vice versa. The result is identical
   to the native behaviour from the viewer's side, works on every browser, and
   needs no permission. Touch input is transformed with the element, so the
   player's own controls stay where they appear to be.

   Turning the phone then hands over to the landscape rule below, which fills
   the screen without any rotation — so both orientations give a full-screen
   landscape picture, which is the whole point.
   ========================================================================== */
@media (orientation: portrait) and (max-width: 991.98px) {
    .watchVideoPage .vxh,
    .watchVideoPage .vxm-tabbar,
    .watchVideoPage .vxf {
        display: none !important;
    }
    .watchVideoPage #playerApp,
    .watchVideoPage .player {
        position: fixed !important;
        top: 50% !important;
        left: 50% !important;
        inset-inline-end: auto !important;
        bottom: auto !important;
        z-index: 2000;
        /* axes swapped: the rotated box is as wide as the viewport is tall */
        width: 100dvh !important;
        height: 100dvw !important;
        max-height: none !important;
        margin: 0 !important;
        padding: 0 !important;
        background: #000;
        transform: translate(-50%, -50%) rotate(90deg);
        transform-origin: center center;
    }
    .watchVideoPage #playerApp .row,
    .watchVideoPage #playerApp [class*="col-"],
    .watchVideoPage .player-ratio {
        height: 100% !important;
        width: 100% !important;
        margin: 0 !important;
        padding: 0 !important;
        border-radius: 0 !important;
    }
    .watchVideoPage .player-ratio > div,
    .watchVideoPage .vdx-fluid,
    .watchVideoPage video.vdx-video {
        width: 100% !important;
        height: 100% !important;
    }
    .watchVideoPage video.vdx-video {
        object-fit: contain;
    }
    body:has(.watchVideoPage) {
        overflow: hidden !important;
    }
}

/* ==========================================================================
   WATCH — landscape is fullscreen playback.

   Rotating a phone on a video page should give the video the whole screen,
   the way a native app does. This is done in CSS rather than through the
   Fullscreen API on purpose: browsers require a user gesture to enter
   fullscreen, and a rotation is not one — requesting it on `orientationchange`
   is rejected, silently, on both iOS and Android. Pinning the player to the
   viewport achieves the same result and cannot be refused.

   Gated on height alone rather than `pointer: coarse`. Coarse-pointer is the
   more expressive signal, but it is not reliably reported everywhere, and a
   media query that silently fails to match leaves the video overflowing the
   screen with no way to tell. 500px separates phones in landscape (360-430
   tall) from laptop windows without needing it — and if a very short desktop
   window does match on a video page, fullscreen playback is the right answer
   there too.
   ========================================================================== */
@media (orientation: landscape) and (max-height: 500px) {
    /* Everything that is not the video gets out of the way. */
    .watchVideoPage .vxh,
    .watchVideoPage .vxm-tabbar,
    .watchVideoPage .vxf,
    .watchVideoPage .video-title-container,
    .watchVideoPage #commentsDiv,
    .watchVideoPage .watchVideoInner > *:not(.player):not(#playerApp) {
        display: none !important;
    }

    .watchVideoPage #playerApp,
    .watchVideoPage .player {
        position: fixed !important;
        inset: 0 !important;
        z-index: 2000;
        width: 100vw !important;
        height: 100dvh !important;
        max-height: none !important;
        margin: 0 !important;
        padding: 0 !important;
        background: #000;
    }
    .watchVideoPage #playerApp .row,
    .watchVideoPage #playerApp [class*="col-"] {
        height: 100% !important;
        margin: 0 !important;
        padding: 0 !important;
    }
    /* The ratio box sizes itself from a 16:9 padding hack; in fullscreen the
       viewport decides the box and the video letterboxes inside it. */
    .watchVideoPage .player-ratio {
        height: 100% !important;
        width: 100% !important;
        padding: 0 !important;
        border-radius: 0 !important;
    }
    .watchVideoPage .player-ratio > div,
    .watchVideoPage .vdx-fluid,
    .watchVideoPage video.vdx-video {
        width: 100% !important;
        height: 100% !important;
    }
    .watchVideoPage video.vdx-video {
        object-fit: contain;
    }
    /* No page scroll behind a fullscreen video. */
    body:has(.watchVideoPage) {
        overflow: hidden !important;
    }
    /* Keep the player clear of the notch / home indicator in landscape. */
    .watchVideoPage .vdx-fluid {
        padding-inline: env(safe-area-inset-left) env(safe-area-inset-right);
    }
}
