/*
 * Shorts feed - a mobile app that happens to run in a browser.
 *
 * One-page stylesheet, injected by the head_extra hook rather than merged into
 * combined.css: nothing else on the site uses any of it.
 *
 * PHONE IS THE PRIMARY TARGET. On a phone the site chrome is stripped entirely
 * (navbar, footer, sidebar, category pills) and the clip owns the whole screen,
 * because a fixed navbar over a full-bleed vertical video is the one thing that
 * instantly breaks the app illusion. Desktop keeps the real site chrome and
 * puts the feed in a phone-shaped frame, so it stays a normal, navigable page.
 *
 * The swipe is CSS scroll-snap, not JavaScript - the gesture stays the
 * browser's own, so momentum and rubber-banding feel native.
 */

/* ---------------------------------------------------------------------------
   WHAT THIS PAGE HIDES - and nothing more.

   Exactly two things come off the shorts page:

     1. the category strip, which filters the video library and is meaningless
        against a shorts feed (hidden here);
     2. the slider ad, which would overlay the video - handled by an adv path
        rule (/shorts/, scope `slider`), NOT by CSS, so that a broken rule
        shows up rather than being quietly masked.

   Everything else stays: the top promo bar, the AI links row, the site navbar,
   the sidebar and the footer. An earlier revision hid those too and that was
   wrong - the page has to stay part of the site, not become a separate app
   that has swallowed the navigation.
   --------------------------------------------------------------------------- */

body.shorts .pills-widget-container { display: none !important; }

/* The feed is exactly the viewport below the chrome, so the PAGE itself must
   never scroll - the only scroller is the feed. Without this the layout is one
   viewport of chrome-plus-feed followed by the footer, which puts a scrollbar
   on a page whose whole point is that you swipe rather than scroll. */
body.shorts { overflow: hidden; }

/* ...and the same on the root element. `overflow: hidden` on <body> alone
   stops the page SCROLLING but <html> still reserves a scrollbar track, which
   is the dead grey gutter that was showing on desktop. :has() lets this stay in
   the one stylesheet instead of needing a class on <html>; browsers without it
   simply keep the old inert track. */
html:has(body.shorts) { overflow: hidden; }

/* PHONE: the fixed navbar comes off so the clip is genuinely full screen, and
   the burger moves into the app bar instead - it opens the SAME drawer, by
   clicking the theme's own #openNavBtn, so there is one menu implementation
   rather than two. Desktop keeps the navbar exactly as it is. */
@media (max-width: 991px) {
    /* Everything above the video goes, so the clip really is full screen:
       the navbar, the pink promo bar and the AI links row. All three sit in
       the flow ABOVE the feed, so leaving any of them shortens the video and
       - because the feed is already a full viewport tall - pushes the page
       into scrolling.

       DESKTOP KEEPS ALL OF IT. This is deliberately mobile-only: on a desktop
       the page must stay an ordinary part of the site with its navigation
       intact. */
    body.shorts .navbar-fixed-top,
    body.shorts .promo-banner,           /* the pink "Free Live Cams" bar */
    body.shorts .promo-content-wrapper,  /* its sidebar sibling */
    body.shorts .topbar-links { display: none !important; }

    body.shorts { padding-top: 0 !important; }
    body.shorts .shorts-burger { display: flex; }
}

/* ...and the content well must not inset the feed. #main is .container-fluid,
   which carries the site's 15px page gutter on ALL FOUR sides. The top one
   showed as a dark band under the navbar; the left/right pair is subtler and
   survived longer - it left the clip 400px wide in a 430px viewport, close
   enough to full-bleed to look like it was meant to be, but a phone app has
   no margin. Zero the lot: this page is the whole screen. */
body.shorts #main {
    padding: 0;
    margin-top: 0;
}
body.shorts #main > .row { margin-top: 0; }

/* ---------------------------------------------------------------------------
   The app surface
   --------------------------------------------------------------------------- */

/* Ambient backdrop: the current clip's own poster, blown up and blurred behind
   the whole window. _shorts.js sets --shorts-backdrop as the current slide
   changes.

   position: FIXED, not absolute - it fills the viewport rather than just the
   feed's own box, so the colour runs behind the navbar and sidebar too instead
   of stopping at the edge of the content well. It escapes .shorts-app's
   `overflow: clip` because a fixed element's containing block is the viewport,
   which is outside the clipper. */
.shorts-app::before {
    content: "";
    position: fixed;
    inset: 0;
    background-image: var(--shorts-backdrop, none);
    background-size: cover;
    background-position: center;
    filter: blur(60px) saturate(1.4);
    transform: scale(1.15);            /* overscan so the blur has no edge */
    opacity: 0;
    transition: opacity .6s ease;
    pointer-events: none;
    z-index: 0;
}
.shorts-app.has-backdrop::before { opacity: 1; }

/* ...and the site's own background laid back over the top of it.
   The blur alone tinted the whole page to whatever the current clip happened
   to be - a brown bathroom made the site brown. This restores the theme's
   colour and its two corner glows as the thing you actually see, leaving the
   clip to show through only as a faint movement behind it. Not blurred: it is
   flat colour and gradients, so there is nothing to soften. */
.shorts-app::after {
    content: "";
    position: fixed;
    inset: 0;
    background-color: rgba(8, 10, 18, .88);   /* --color-surface-deep */
    background-image:
        radial-gradient(900px 500px at 12% -8%, rgba(255, 46, 151, .10), transparent 60%),
        radial-gradient(900px 500px at 100% -5%, rgba(168, 85, 247, .10), transparent 60%);
    opacity: 0;
    transition: opacity .6s ease;
    pointer-events: none;
    z-index: 0;
}
.shorts-app.has-backdrop::after { opacity: 1; }
.shorts-feed, .shorts-bar, .shorts-nav, .shorts-loading { position: relative; z-index: 1; }

.shorts-app {
    position: relative;
    /* Fill whatever viewport is left BELOW the site chrome.
       --shorts-offset is the element's distance from the top of the viewport,
       measured by _shorts.js on load and resize - the navbar, promo bar and AI
       row are all still on the page, and their combined height is not
       something CSS can guess at. Falls back to the full viewport if the
       script has not run.
       dvh, not vh: on mobile the URL bar collapses and 100vh leaves the last
       slide cropped underneath it. */
    height: calc(100dvh - var(--shorts-offset, 0px));
    background: #000;

    /* clip, NOT hidden. `overflow: hidden` still makes this a SCROLL CONTAINER
       - it only hides the scrollbar - and its content runs ~115px past its
       height, so scrollIntoView() on a slide happily scrolled this box too
       (that method scrolls every scrollable ancestor, not just the nearest).
       On desktop the feed is centred inside here, so one press of the down
       arrow slid the whole app up by the centring offset and took the app bar
       off the top of the screen for good. `overflow: clip` creates no scroll
       container at all, so there is nothing here left to scroll.
       hidden first as the fallback for anything that lacks clip. */
    overflow: hidden;
    overflow: clip;
    /* Notch and home-indicator safe areas. */
    --shorts-safe-top: env(safe-area-inset-top, 0px);
    --shorts-safe-bottom: env(safe-area-inset-bottom, 0px);
}

.shorts-feed {
    height: 100%;
    overflow-y: scroll;
    overflow-x: hidden;
    scroll-snap-type: y mandatory;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-y: contain;   /* never drag the page behind the feed */
    scrollbar-width: none;
}
.shorts-feed::-webkit-scrollbar { display: none; }

.shorts-slide {
    position: relative;
    height: 100%;
    scroll-snap-align: start;
    scroll-snap-stop: always;         /* never fly past a clip in one flick */
    display: flex;
    align-items: center;
    justify-content: center;
    background: #000;
    overflow: hidden;
}

/* Placeholder fill.
   The thumbnail is landscape (300x180) and these clips are portrait, so it is
   drawn cover-sized and blurred BEHIND the video rather than as its poster -
   a landscape poster in a portrait box is what made the frame change shape
   when playback started. It is what the viewer looks at until the first frame
   decodes, so it is still worth keeping now that the video itself covers. */
.shorts-slide::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image: var(--slide-thumb, none);
    background-size: cover;
    background-position: center;
    filter: blur(22px) saturate(1.3) brightness(.55);
    transform: scale(1.15);          /* hide the blur's soft edge */
    z-index: 0;
    pointer-events: none;
}

/* Full-bleed everywhere - phone screen or desktop frame, the clip fills the
   width the way it does in a native app rather than letterboxing inside it.
   Same rule on both: the desktop frame IS a phone, so there is no reason for
   it to fit the picture differently. */
.shorts-video {
    position: relative;              /* sit above the placeholder */
    z-index: 1;

    /* WIDTH IS THE FIXED DIMENSION, height follows the clip's own ratio.
       Not `height: 100%; object-fit: cover` - a modern phone screen is around
       0.46 wide-over-tall while these clips are 9:16 (0.5625), so cover scaled
       them to fill the HEIGHT and threw ~20% of the picture off the left and
       right edges, watermark included. Losing the sides of the frame is worse
       than a margin: it is the part of the shot the uploader composed.

       Anything left over vertically is the blurred backdrop below, which is
       what it is there for. A clip TALLER than the screen still overflows and
       is clipped by the slide - cropping top/bottom is the acceptable trade,
       cropping the sides is not. */
    width: 100%;
    height: auto;

    /* Belt and braces: the box above comes from the width/height ATTRIBUTES
       (so it is correct before a single byte is fetched, which is what stops
       the frame changing shape on play). If a file's real ratio ever differs
       from what #__video_files recorded, letterbox inside that box rather
       than crop - same principle as the rule above. */
    object-fit: contain;

    /* transparent, NOT #000: the blurred fill has to show through while the
       clip is still decoding. */
    background: transparent;
    display: block;
}

/* Full-screen tap target for play/pause and first-tap-to-unmute. A real
   button so it is keyboard reachable; sits under the overlay so links win. */
.shorts-tap {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    padding: 0;
    background: transparent;
    cursor: pointer;
    z-index: 1;
}

/* ---------------------------------------------------------------------------
   App bar
   --------------------------------------------------------------------------- */

.shorts-bar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;      /* row 1 identity, row 2 tabs */
    align-items: stretch;
    gap: 8px;
    padding: calc(var(--shorts-safe-top) + 10px) 14px 22px;
    color: #fff;
    /* Gradient, not a bar: the video keeps running under it. */
    background: linear-gradient(to bottom, rgba(0,0,0,.6) 0%, rgba(0,0,0,.25) 55%, transparent 100%);
    pointer-events: none;
}
.shorts-bar a { pointer-events: auto; }

/* Row 1: brand on the left, actions on the right. */
.shorts-bar-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* Brand lockup: logo + "Shorts" reading as one wordmark, top left. */
.shorts-brand {
    display: flex;
    align-items: center;
    gap: 7px;
    pointer-events: auto;
    text-decoration: none;
    min-width: 0;
}
.shorts-brand:hover { text-decoration: none; }
.shorts-brand:hover .shorts-bar-logo { opacity: .85; }

/* The shipped logo is pink/purple. brightness(0) crushes it to black and
   invert(1) lifts that to pure white, so it goes white whatever colours the
   source has - no second asset to keep in sync when the logo changes. */
.shorts-bar-logo {
    height: 22px;
    width: auto;
    display: block;
    filter: brightness(0) invert(1);
    transition: opacity .15s;
}

.shorts-bar-title {
    font-size: 16px;
    font-weight: 700;
    letter-spacing: .02em;
    color: #fff;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .7);
    white-space: nowrap;
}

/* Pushes the actions to the right edge. */
.shorts-bar-spacer { flex: 1; }

/* Feed switcher. Centred between two spacers so it sits in the middle of the
   bar whatever the brand and actions either side of it measure - the app-bar
   convention, and it stops the tabs shifting when the logo loads. */
.shorts-tabs {
    display: flex;
    justify-content: center;
    gap: 22px;
    pointer-events: auto;
}
.shorts-tab {
    position: relative;
    padding: 4px 2px;
    color: rgba(255, 255, 255, .62);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: .01em;
    white-space: nowrap;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .8);
    transition: color .15s;
}
.shorts-tab:hover, .shorts-tab:focus { color: #fff; }
.shorts-tab.is-on { color: #fff; }

/* Underline the active tab rather than filling it: a pill would read as a
   button, and these navigate. */
.shorts-tab.is-on::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -2px;
    height: 2px;
    border-radius: 2px;
    background: #fff;
}

/* Following with nothing to show. Sits over the (empty) feed rather than
   replacing it, so the app bar and its tabs stay put and the way out is one
   tap away. */
.shorts-empty {
    position: absolute;
    inset: 0;
    z-index: 3;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 0 32px;
    text-align: center;
    color: var(--color-text-secondary, #98a2b8);
}
.shorts-empty i { font-size: 34px; color: rgba(255, 255, 255, .35); }
.shorts-empty p { margin: 0; max-width: 34ch; font-size: 15px; line-height: 1.5; }
.shorts-empty-btn {
    padding: 10px 22px;
    border-radius: 999px;
    background: var(--color-accent-pink, #ff2e97);
    color: #fff;
    font-weight: 700;
    font-size: 14px;
}
.shorts-empty-btn:hover { color: #fff; filter: brightness(1.08); }
.shorts-empty-link { color: rgba(255, 255, 255, .6); font-size: 13px; text-decoration: underline; }
.shorts-empty-link:hover { color: #fff; }

/* Phones: the bar has a burger and an action on it too, so the tab type steps
   down rather than wrapping the bar onto a second line. */
@media (max-width: 480px) {
    .shorts-tabs { gap: 18px; }
    .shorts-tab { font-size: 14px; }
}

/* Burger - phone only, opens the theme's existing drawer. */
.shorts-burger {
    display: none;                    /* shown at <=991px */
    width: 38px;
    height: 38px;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, .3);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    color: #fff;
    font-size: 16px;
    cursor: pointer;
    pointer-events: auto;
}
.shorts-burger:hover { background: rgba(0, 0, 0, .5); }

/* Mute toggle - a bar control, matching the burger beside it.
   It began life as an indicator floating over the clip with
   pointer-events:none, so taps went through to the play/pause layer below it,
   and it was stamped onto every slide. It is one real button in the bar now:
   sound is a setting for the feed, not a property of a clip.

   It must stay visible whichever way the sound is set - a control that
   vanishes once you have used it cannot be used to undo what you did. */
.shorts-sound {
    border: 0;
    cursor: pointer;
    font: inherit;
    padding: 0;
    flex-shrink: 0;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, .3);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    color: #fff;
    font-size: 16px;
    pointer-events: auto;
    transition: background .2s;
}
.shorts-sound:hover { background: rgba(0, 0, 0, .5); }

/* Paused: a large centred glyph, the universal signal. */
.shorts-slide.is-paused::after {
    content: "\f04b";                 /* fa-play */
    /* "Font Awesome 5 Free" + weight 900, NOT the Font Awesome 4 family name
       `FontAwesome` - the theme ships FA5, so the FA4 name matches no loaded
       font and the glyph silently rendered as nothing. */
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    font-size: 52px;
    color: rgba(255, 255, 255, .9);
    text-shadow: 0 2px 16px rgba(0, 0, 0, .7);
    pointer-events: none;
    z-index: 2;
}

/* ---------------------------------------------------------------------------
   Overlay: meta bottom-left, actions bottom-right
   --------------------------------------------------------------------------- */

.shorts-overlay {
    position: absolute;
    inset: auto 0 0 0;
    z-index: 3;
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 14px;
    padding: 70px 14px calc(var(--shorts-safe-bottom) + 20px);
    background: linear-gradient(to top, rgba(0,0,0,.8) 0%, rgba(0,0,0,.35) 45%, transparent 100%);
    pointer-events: none;
}
/* The overlay itself is click-through so taps reach the play/pause layer under
   it. Anything meant to be CLICKED has to opt back in by name - .shorts-subscribe
   is neither an <a> nor a .shorts-action, and without it here every press on the
   + badge passed through to .shorts-tap and merely paused the clip. */
.shorts-overlay a,
.shorts-overlay .shorts-action,
.shorts-overlay .shorts-subscribe { pointer-events: auto; }

.shorts-info { min-width: 0; flex: 1; }

.shorts-title {
    color: #fff;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.35;
    margin-bottom: 5px;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .85);
    /* two lines maximum - a long title must never eat the frame */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.shorts-title:hover { color: #fff; text-decoration: underline; }

.shorts-user {
    display: inline-block;
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    margin-bottom: 7px;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .85);
}
.shorts-user:hover { color: #fff; opacity: .85; }

.shorts-stats {
    display: flex;
    gap: 14px;
    color: rgba(255, 255, 255, .8);
    font-size: 12px;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .85);
}
.shorts-stats i { margin-right: 4px; }

/* The action rail every short-form feed uses: circular, thumb-reachable. */
.shorts-actions {
    display: flex;
    flex-direction: column;
    gap: 18px;
    align-items: center;
    flex: 0 0 auto;
}
.shorts-action {
    /* The button reset lives HERE, not on .shorts-like.
       It used to be written per class, so every action added later came out
       with the browser's default control chrome - a light grey box and a 2px
       outset border around the icon and its label. Anything that carries
       .shorts-action is a bare icon button by definition. */
    border: 0;
    padding: 0;
    background: transparent;
    cursor: pointer;
    font: inherit;
    -webkit-appearance: none;
    appearance: none;

    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    color: #fff;
    font-size: 11px;
    font-weight: 600;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .85);
}
.shorts-action i {
    font-size: 20px;
    width: 46px;
    height: 46px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: rgba(255, 255, 255, .14);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: background .15s, transform .15s;
}
.shorts-action:hover { color: #fff; }
.shorts-action:hover i { background: rgba(255, 255, 255, .26); transform: scale(1.06); }
.shorts-action:active i { transform: scale(.94); }
.shorts-action.is-static { cursor: default; }
.shorts-action.is-static:hover i { background: rgba(255, 255, 255, .14); transform: none; }

/* Uploader avatar - the top of the rail, as in every short-form feed. */
.shorts-avatar {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, var(--color-accent-pink, #ff2e97), var(--color-accent-purple, #a855f7));
    border: 2px solid rgba(255, 255, 255, .9);
    color: #fff;
    font-size: 19px;
    font-weight: 700;
    line-height: 1;
    text-decoration: none;
    box-shadow: 0 2px 10px rgba(0, 0, 0, .45);
    transition: transform .15s;
}
.shorts-avatar:hover { color: #fff; transform: scale(1.06); }
.shorts-avatar:active { transform: scale(.94); }
.shorts-avatar-initial { pointer-events: none; }

/* Subscribe, as the + badge on the avatar rather than a row of its own.
   Overlaps the bottom edge of the avatar and turns into a tick once you are
   subscribed, which is the arrangement every short-form feed uses. */
.shorts-avatar-wrap {
    position: relative;
    display: flex;
    justify-content: center;
    margin-bottom: 6px;
}

.shorts-subscribe {
    position: absolute;
    left: 50%;
    bottom: -9px;
    transform: translateX(-50%);

    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 0;
    padding: 0;
    background: var(--color-accent-pink, #ff2e97);
    color: #fff;
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
    transition: background .15s, transform .15s;
}
.shorts-subscribe:hover { transform: translateX(-50%) scale(1.12); }

/* A 22px circle is a small target, especially on a phone. This pseudo-element
   enlarges the HIT AREA to ~44px (the usual minimum) without changing anything
   visible - a click on it still reports the button as the target, so the JS is
   unaffected. */
.shorts-subscribe::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
    border-radius: 50%;
}

/* Subscribed: green tick, and it stops inviting a click. The + and the tick are
   separate elements so only the state class has to change. */
.shorts-subscribe .fa-check { display: none; }
.shorts-subscribe.is-active { background: rgba(80, 200, 120, .95); }
.shorts-subscribe.is-active .fa-plus { display: none; }
.shorts-subscribe.is-active .fa-check { display: block; }

/* The badge sits ON the avatar, so it must not inherit the rail's icon chrome. */
.shorts-subscribe i {
    width: auto;
    height: auto;
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-radius: 0;
    font-size: 12px;
}

/* The model this clip is attributed to. */
.shorts-model {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    margin-bottom: 7px;
    padding: 4px 11px 4px 9px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .16);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    color: #fff;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.shorts-model:hover { color: #fff; background: rgba(255, 255, 255, .28); }
.shorts-model i { font-size: 11px; opacity: .9; }

/* Share and Like are <button>s, so they need the anchor styling reset onto
   them - the rail is otherwise anchors, which carry none of the UA's button
   chrome. */
.shorts-share,
.shorts-like {
    border: 0;
    padding: 0;
    background: transparent;
    cursor: pointer;
    font: inherit;
}
.shorts-share.is-copied i { background: rgba(80, 220, 130, .35); }

/* Liked: the accent, filled, and held there. Disabled the moment it succeeds,
   so it must NOT take the greyed-out look a disabled control usually gets -
   this is a completed action, not an unavailable one. */
.shorts-like.is-liked i {
    background: var(--color-accent-pink, #ff2e97);
    color: #fff;
    transform: scale(1.06);
}
.shorts-like:disabled { cursor: default; opacity: 1; }
.shorts-like:disabled:hover i { transform: none; }
.shorts-like.is-liked:hover i { background: var(--color-accent-pink, #ff2e97); transform: scale(1.06); }

/* Dislike gets the same treatment in a cooler colour, so the rail reads at a
   glance: pink thumb = liked, blue thumb = disliked, plain = not voted. Before
   this the handler set .is-active but NOTHING styled it, so a dislike looked
   identical to no dislike and the click appeared to do nothing. */
.shorts-dislike.is-active i {
    background: var(--color-accent-blue, #3ea6ff);
    color: #fff;
    transform: scale(1.06);
}
.shorts-dislike:disabled { cursor: default; opacity: 1; }
.shorts-dislike:disabled:hover i { transform: none; }
.shorts-dislike.is-active:hover i { background: var(--color-accent-blue, #3ea6ff); transform: scale(1.06); }

/* Favourited. Same reasoning as the two above - the handler set .is-active and
   nothing styled it, so the button gave no sign it had worked. */
.shorts-favorite.is-active i {
    background: var(--color-accent-pink, #ff2e97);
    color: #fff;
    transform: scale(1.06);
}
.shorts-favorite:disabled { cursor: default; opacity: 1; }
.shorts-favorite:disabled:hover i { transform: none; }
.shorts-favorite.is-active:hover i { background: var(--color-accent-pink, #ff2e97); transform: scale(1.06); }

/* Guests cannot comment when video.allow_comment is '1' (registered only), so
   the composer is replaced by this prompt rather than a textarea that accepts
   typing and then fails. */
.shorts-compose-login {
    display: block;
    text-align: center;
    padding: 14px 16px;
    font-size: 13px;
    color: rgba(255, 255, 255, .75);
}
.shorts-compose-login a { color: var(--color-accent-pink, #ff2e97); }

/* Report form radios.
   The server markup is Bootstrap 3's .radio > label > input pattern, which
   positions the input with margin-left:-20px against a padded label. Inside
   this sheet that pushes it off the panel's left edge and it renders as a
   sliver. Position it explicitly instead of relying on the negative margin. */
.shorts-sheet-body .radio,
.shorts-sheet-body .checkbox {
    position: relative;
    display: block;
    padding-left: 28px;
    margin: 10px 0;
}
.shorts-sheet-body .radio label,
.shorts-sheet-body .checkbox label {
    padding-left: 0;
    margin: 0;
    cursor: pointer;
    font-weight: 400;
}
.shorts-sheet-body .radio input[type="radio"],
.shorts-sheet-body .checkbox input[type="checkbox"] {
    position: absolute;
    left: 2px;
    top: 50%;
    transform: translateY(-50%);
    margin: 0;
    width: 16px;
    height: 16px;
    accent-color: var(--color-accent-pink, #ff2e97);
    cursor: pointer;
}

/* The rest of the report form: the theme's .form-control styling does not
   reach inside this panel, so inputs came out unstyled. */
.shorts-sheet-body .control-label { display: block; margin-bottom: 6px; font-weight: 600; }
.shorts-sheet-body .form-group { margin-bottom: 16px; }

/* Failure message (rating disabled, or "please log in to rate"). Centred over
   the clip and self-dismissing - the feed has no room for a permanent alert. */
.shorts-toast {
    position: absolute;
    left: 50%;
    bottom: 24%;
    transform: translate(-50%, 8px);
    z-index: 9;
    max-width: min(92%, 420px);
    padding: 10px 16px;
    border-radius: 22px;
    background: rgba(15, 15, 20, .92);
    color: #fff;
    font-size: 13px;
    line-height: 1.35;
    text-align: center;
    box-shadow: 0 8px 28px rgba(0, 0, 0, .5);
    opacity: 0;
    pointer-events: none;
    transition: opacity .2s, transform .2s;
}
.shorts-toast.is-on { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.shorts-toast a { color: var(--color-accent-pink, #ff2e97); font-weight: 700; }

/* --- ad slot ---------------------------------------------------------- */

.shorts-slide--ad { background: #0a0a0a; }
.shorts-ad-inner {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Pending = nothing filled it. Kept out of the flow so the reader never
   scrolls into a blank screen; _shorts.js removes it when no tag exists. */
.shorts-slide--ad.is-pending { display: none; }

/* ---------------------------------------------------------------------------
   DESKTOP: a phone on a page.
   The site chrome stays (this must remain a navigable page), and the feed sits
   in a phone-shaped frame instead of stretching a portrait clip across a
   1080p monitor.
   --------------------------------------------------------------------------- */

@media (min-width: 992px) {
    /* Arrows hug the frame instead of floating out at the window edge, where
       they read as page furniture rather than as controls for the video. */
    .shorts-nav {
        right: auto;
        left: calc(50% + var(--frame-w) / 2 + 18px);
    }

    .shorts-app {
        /* The phone frame, derived ONCE. The bar, the overlay and the sound
           button all have to line up with the feed's edges, so they read these
           rather than repeating the formula - four copies of it is how the
           frame drifts out of alignment the next time it is tuned.

           Both dimensions are capped, and the caps are each other's 9:16 pair
           (420 / 0.5625 = 746). Previously only the width was, so on a tall
           window the frame kept growing past 9:16 and the clip pillarboxed
           inside it. */
        /* Grow into whatever room the screen has, rather than stopping at a
           fixed 420x746 and leaving a big desktop window mostly empty.

           WIDTH is derived first, from every constraint at once, and the height
           then follows from it - that way the frame is always exactly 9:16 and
           there is no second cap to fall out of step with the first:

             (100vh - 190px) * 0.5625   the tallest 9:16 frame that fits under
                                        the header and top bars
             100vw - 340px              leaves the sidebar and the control rail
                                        their space on a narrow-but-tall window
             1080px                     a portrait clip is 1080 wide at most, so
                                        past this we would only be upscaling

           Height binds first on virtually every real screen, which is the
           behaviour you want: the taller the window, the bigger the player. */
        --frame-w: min(calc((100vh - 190px) * 0.5625), calc(100vw - 340px), 1080px);
        --frame-h: calc(var(--frame-w) / 0.5625);

        height: calc(100dvh - var(--shorts-offset, 0px));
        min-height: 560px;
        /* transparent, so the ambient backdrop and the site's own background
           show through. An opaque box here would sit on top of both and
           reinstate the flat slab this page keeps trying to avoid. */
        background: transparent;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 16px;
    }

    .shorts-feed {
        width: var(--frame-w);
        height: var(--frame-h);
        border-radius: 18px;
        box-shadow: 0 18px 60px rgba(0, 0, 0, .6);
        overflow-y: scroll;
    }

    .shorts-slide { border-radius: 18px; }

    .shorts-bar {
        width: var(--frame-w);
        left: 50%;
        right: auto;
        transform: translateX(-50%);
        border-radius: 18px 18px 0 0;
        padding-top: 12px;
    }

    .shorts-overlay {
        width: var(--frame-w);
        left: 50%;
        right: auto;
        transform: translateX(-50%);
        border-radius: 0 0 18px 18px;
    }

    /* No .shorts-sound rule here any more. It used to be absolutely positioned
       over the clip, so desktop had to re-derive its position from the frame;
       it now sits in the bar's flex row, which is already frame-aligned. */
}

/* Up/down controls: desktop only - on touch the gesture is the control. */
.shorts-nav {
    display: none;
    position: absolute;
    right: 6%;
    top: 50%;
    transform: translateY(-50%);
    flex-direction: column;
    gap: 12px;
    z-index: 6;
}
@media (min-width: 1200px) { .shorts-nav { display: flex; } }

.shorts-nav-btn {
    width: 46px;
    height: 46px;
    border-radius: 50%;
    border: 0;
    background: rgba(255, 255, 255, .12);
    color: #fff;
    font-size: 16px;
    cursor: pointer;
    transition: background .15s, transform .15s;
}
.shorts-nav-btn:hover { background: rgba(255, 255, 255, .24); transform: scale(1.06); }
.shorts-nav-btn:active { transform: scale(.94); }

.shorts-loading {
    position: absolute;
    bottom: calc(var(--shorts-safe-bottom) + 16px);
    left: 50%;
    transform: translateX(-50%);
    z-index: 7;
    color: rgba(255, 255, 255, .85);
    font-size: 20px;
}

.shorts-sentinel { height: 1px; }

/* Keyboard users must still see where they are. */
.shorts-tap:focus-visible,
.shorts-nav-btn:focus-visible,
.shorts-bar a:focus-visible,
.shorts-action:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .shorts-feed { scroll-behavior: auto; }
    .shorts-nav-btn,
    .shorts-action i { transition: none; }
}

/* ---------------------------------------------------------------------------
   SHORTS RESULTS GRID  (/search/shorts/)

   A results page, not the feed: the reader is scanning, so this is a grid of
   portrait cards rather than a full-screen player. Each card links into
   /shorts/<id>/, which drops them into the feed on that clip.

   This file is loaded by the feed too, and everything above it is scoped to
   .shorts-app or body.shorts - the grid below cannot reach either.

   THE PAGE CHROME AROUND THE GRID IS NOT DEFINED HERE. The results and category
   pages use .content-browse / .browse-header / .search-topbar from the main
   stylesheet, exactly as video_browse, photo_browse and the search pages do,
   so a visitor moving between the tabs of one category never sees the furniture
   change. A .shorts-results / .shorts-results-head / .shorts-results-count set
   used to live here and is what let the shorts title drift into plain white
   above the tabs while the other two were gradient text beside them. Do not
   reintroduce page-level layout in this file.
   --------------------------------------------------------------------------- */

/* -----------------------------------------------------------------------------
   The shorts grid.

   The cards themselves are the site's ORDINARY video cards - display_shorts()
   emits .video-card-grid > .thumbnail markup, identical to the video grid - so
   everything here is shape only: narrower columns because the cards are
   portrait, and a 9:16 thumb instead of 16:9. Border, shadow, hover lift, badge
   placement, caption and hover preview all come from the video card rules and
   are deliberately NOT repeated, so the two stay in step.
   -------------------------------------------------------------------------- */
.shorts-grid {
    /* Portrait cards fit more per row than landscape ones. */
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}

/* The one real difference: the frame is portrait.

   The ratio is put on the CARD, not on the thumb, so that the card as a whole -
   picture plus caption - is the same shape as the player, which is what you are
   comparing it against when both are on screen. Putting 9:16 on the thumb alone
   made the picture right but the card noticeably longer than a short, because
   the title and the view count are added underneath it.

   The thumb then takes whatever is left after the caption, and crops to fill.
   That trims a little off the top and bottom of the frame rather than letting
   the card grow. */
.shorts-grid .thumbnail {
    aspect-ratio: 9 / 16;
}

.shorts-grid .thumb-wrapper {
    flex: 1 1 auto;
    min-height: 0;      /* a flex item will not shrink below content without it */
}

/* Fill the portrait frame.
   The video grid's own rule is `height: 100%`, which never actually resolves
   against a box whose height comes from aspect-ratio - it just goes unnoticed
   there because a 16:9 frame and a 300x180 thumb are within a few percent of
   each other, so the image lands at the right size by coincidence. Drop the
   same thumb into a 9:16 frame and it covers barely a third of it. Pinning the
   image to the box and cropping is what the old shorts card did with
   background-size: cover, so the framing is unchanged - only the element is. */
.shorts-grid .thumb-wrapper img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* A 9:16 thumb is much taller than a 16:9 one, so the fixed minimum that keeps
   landscape cards even would leave a large gap under a portrait card. The
   caption still needs its own reserved height, which .thumbnail already sets. */
.shorts-grid .thumbnail {
    min-height: 0;
}

@media (max-width: 600px) {
    .shorts-grid {
        grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    }
}

/* -----------------------------------------------------------------------------
   The action sheet: comments, report, add-to-playlist.

   One panel serves all three. It slides up over the lower half of the frame the
   way a native short-form app does, rather than opening a page - the clip keeps
   playing behind it, which is the whole reason for doing this in the overlay
   instead of sending people to the watch page.

   Positioned against the frame, not the viewport, so on desktop it sits inside
   the phone-shaped player rather than flying to the edges of a wide monitor.
   -------------------------------------------------------------------------- */
.shorts-sheet {
    position: absolute;
    left: 50%;
    bottom: 0;
    transform: translateX(-50%);
    z-index: 40;

    display: flex;
    flex-direction: column;

    width: var(--frame-w, 100%);
    max-height: 70%;

    background: var(--color-surface, #12151f);
    border: 1px solid var(--color-border, rgba(255, 255, 255, .08));
    border-bottom: 0;
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -18px 50px rgba(0, 0, 0, .6);
}

.shorts-sheet[hidden] { display: none; }

.shorts-sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border, rgba(255, 255, 255, .08));
    flex: 0 0 auto;
}

.shorts-sheet-title {
    margin: 0;
    font-size: 15px;
    font-weight: 700;
    color: var(--color-text-primary, #e8eaf0);
}

.shorts-sheet-close {
    background: none;
    border: 0;
    padding: 6px;
    font-size: 18px;
    line-height: 1;
    color: var(--color-text-secondary, #9aa3b5);
    cursor: pointer;
}
.shorts-sheet-close:hover { color: var(--color-text-primary, #e8eaf0); }

/* The only scrolling region - the head and the composer stay put. */
.shorts-sheet-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;   /* do not scroll the FEED behind it */
    padding: 12px 16px;
    color: var(--color-text-secondary, #9aa3b5);
    font-size: 14px;
}

/* The endpoints render their own markup, so give their elements sane defaults
   rather than restyling each one. */
.shorts-sheet-body img { max-width: 100%; height: auto; }
.shorts-sheet-body label { display: block; margin: 6px 0 2px; color: var(--color-text-primary, #e8eaf0); }
.shorts-sheet-body input[type="text"],
.shorts-sheet-body textarea,
.shorts-sheet-body select {
    width: 100%;
    background: var(--color-surface-sunken, #0b0d14);
    border: 1px solid var(--color-border, rgba(255, 255, 255, .08));
    border-radius: 8px;
    color: var(--color-text-primary, #e8eaf0);
    padding: 8px 10px;
}
.shorts-sheet-body .btn { margin-top: 10px; }

.shorts-sheet-compose {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    padding: 10px 16px 14px;
    border-top: 1px solid var(--color-border, rgba(255, 255, 255, .08));
    flex: 0 0 auto;
}
.shorts-sheet-compose[hidden] { display: none; }

.shorts-sheet-compose textarea {
    flex: 1 1 auto;
    resize: none;
    max-height: 96px;
    background: var(--color-surface-sunken, #0b0d14);
    border: 1px solid var(--color-border, rgba(255, 255, 255, .08));
    border-radius: 18px;
    color: var(--color-text-primary, #e8eaf0);
    padding: 9px 14px;
    font-size: 14px;
    line-height: 1.35;
}

.shorts-sheet-compose .btn { padding: 8px 14px; border-radius: 18px; }

/* Tap-anywhere-else to close, and it stops taps reaching the play/pause target
   underneath - without it, closing the sheet would also pause the clip. */
.shorts-sheet-scrim {
    position: absolute;
    inset: 0;
    z-index: 39;
    background: rgba(0, 0, 0, .45);
}
.shorts-sheet-scrim[hidden] { display: none; }

/* A comment row, for the markup comment_pagination returns. */
.shorts-sheet-body .comment,
.shorts-sheet-body .media {
    padding: 10px 0;
    border-bottom: 1px solid var(--color-border, rgba(255, 255, 255, .06));
}
.shorts-sheet-body .comment:last-child,
.shorts-sheet-body .media:last-child { border-bottom: 0; }

@media (max-width: 991px) {
    /* No fixed frame on a phone - the sheet spans the screen. */
    .shorts-sheet { width: 100%; max-height: 65%; }
}

/* The reply from an endpoint - "Please log in to add to your favourites",
   "already reported". These were being created and removed with no styling at
   all, so every refusal looked like the button simply doing nothing. */
.shorts-toast {
    position: absolute;
    left: 50%;
    bottom: 90px;
    transform: translateX(-50%);
    z-index: 60;

    max-width: min(90%, 340px);
    padding: 10px 16px;
    border-radius: 22px;
    background: rgba(10, 12, 18, .95);
    border: 1px solid var(--color-border, rgba(255, 255, 255, .12));
    color: #fff;
    font-size: 13px;
    line-height: 1.35;
    text-align: center;
    box-shadow: 0 8px 30px rgba(0, 0, 0, .6);
    pointer-events: none;
}
