/* ============================================================================
   Scroll Reel — /scroll-reel
   ----------------------------------------------------------------------------
   Two genuinely different layouts, not one layout stretched:

   MOBILE (< 900px)  A full-bleed vertical reel. One video fills the viewport,
                     scroll-snaps like the native Shorts/Reels experience the
                     teasers were authored for (1080x1920). The caption does NOT
                     overlay the clip: the teaser videos already carry burned-in
                     captions rendered into the lower third of the frame, so an
                     overlaid headline collided with them and both became
                     unreadable (reported 2026-08-08). The caption is a solid
                     block BELOW the video instead; the item is a flex column so
                     video + caption together fill exactly one snap page.

   DESKTOP (>= 900px) The same 9:16 clip would look broken edge-to-edge on a
                     1440px viewport, so the item becomes a centered STAGE: the
                     video is capped at 9:16 / 78vh with rounded corners and a
                     blurred, darkened copy of the poster behind it filling the
                     dead space, and the headline + summary + read-article CTA
                     move OUT of the video into a readable column beside it.
                     Snap scrolling is kept so it still reads as a reel.

   Everything inherits the site's CSS custom properties (--bg, --text, --border,
   --accent) so light/dark theme switching is automatic.
   ========================================================================= */

.reel-page {
  /* CLIP, NOT HIDDEN. `overflow-x: hidden` makes <body> a scroll container in
     BOTH axes (the y axis computes to `auto`), and a sticky element's nearest
     scroll container is what it sticks to. So `.site-top` — `position: sticky;
     top: 0` — stopped sticking to the viewport and started sticking to <body>,
     which never scrolls; the document scroll then carried the whole header off
     screen and it never came back. Measured on dev 2026-08-08 at 390x844:
     after a 300px document scroll `#site-top` sat at top: -300 and stayed
     there, so the logo, ticker and hamburger were gone until a hard refresh
     (reported by Nicolas). `clip` suppresses the same horizontal overflow
     WITHOUT creating a scroll container, so sticky keeps addressing the
     viewport. Applies to desktop as well, where the header had the same
     silent break. */
  overflow-x: clip;
}

.reel-main {
  padding: 0 0 24px;
}

/* Compact by design: this block sits between the site header and the first
   clip, so every pixel it takes pushes the reel below the fold on a phone.
   Title and one-line subtitle only. */
.reel-intro {
  padding: 10px 24px 8px;
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 12px;
}

.reel-title {
  margin: 0;
  font-size: 1.15rem;
  letter-spacing: -0.02em;
}

.reel-sub {
  margin: 0;
  color: var(--text-muted, #8b95a5);
  font-size: 0.82rem;
  /* One line, ellipsised — never wraps into a second row. */
  min-width: 0;
  flex: 1 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------------------------------------------------------------- stage --- */

.reel-stage {
  position: relative;
  display: block;
  scroll-snap-type: y mandatory;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.reel-stage::-webkit-scrollbar { display: none; }

.reel-item {
  position: relative;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: block;
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  isolation: isolate;
}

/* Blurred backdrop: a scaled, blurred copy of the article image behind the
   clip, so letterbox space reads as designed rather than as empty black. */
.reel-backdrop {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  filter: blur(38px) saturate(1.3) brightness(0.42);
  transform: scale(1.18);
  z-index: 0;
  pointer-events: none;
}

/* NOTE: no bottom scrim on mobile. The caption is no longer overlaid on the
   clip, so a scrim would only darken the video's own burned-in captions.
   Desktop adds its own side scrim inside its media query. */

/* The whole item is an <a> inside <main>, so `.static-page main a` (0,1,2)
   would underline the headline, summary and CTA and recolor them link-blue.
   These selectors are (0,2,2)/(0,2,3) so they win without !important. */
.reel-page main a.reel-item,
.reel-page main a.reel-item:hover,
.reel-page main a.reel-item:visited,
.reel-page main a.reel-item * {
  text-decoration: none;
  color: inherit;
}

.reel-video {
  display: block;
  /* Transparent so the blurred backdrop shows through the letterbox area and
     during the pre-playback moment, instead of a hard black slab. */
  background: transparent;
  z-index: 1;
  position: relative;
}

.reel-caption {
  position: relative;
  z-index: 2;
}

.reel-headline {
  margin: 0 0 8px;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.25;
}

.reel-summary {
  margin: 0 0 14px;
  line-height: 1.5;
}

.reel-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.85;
  margin-bottom: 8px;
}

.reel-cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  font-size: 0.92rem;
}

/* THE ONE SOUND CONTROL. There is no second one, and it is SMALL in every
   state.

   History, so this does not get re-grown: first there was a per-card
   .reel-mute icon AND a large red "🔇 Tap for sound" pill, which overlapped
   each other and asked the same question twice. The per-card icon went, but
   the pill remained — and a full-width red pill floating over the clip and
   the headline is exactly what Nicolas did not want (2026-08-08). The pill is
   now gone too.

   What is left is one fixed 40px round icon in the top-right corner of the
   clip: 🔇 while muted, 🔊 once sound is on. It does not need to be loud,
   because nobody has to find it — sound turns itself on at the first
   interaction of any kind (unmuteOnFirstGesture). The icon exists as a
   fallback and as the way back to silence.

   Top-right, not bottom-right, because the shared push-prompt bell is pinned
   bottom-right just above the caption panel; two round controls in one corner
   is the duplicate-looking mess all over again.

   Fixed rather than per-card so auto-advance never rebuilds it and there can
   never be more than one on screen. */
.reel-sound-btn {
  position: fixed;
  right: 14px;
  top: calc(var(--reel-chrome, 116px) + 12px);
  z-index: 320; /* above the caption panel and the shared push bell */
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.35);
  background: rgba(0,0,0,0.5);
  color: #fff;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: none;
  animation: none;
}

.reel-sound-btn:hover { background: rgba(0,0,0,0.72); }

/* Only rendered when autoplay is refused outright (iOS Low Power Mode, Safari
   "Auto-Play: Never", data saver). The card is a link, so without a real
   control there is no way to start the clip — a tap would just navigate. */
.reel-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 3;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.45);
  background: rgba(0,0,0,0.55);
  color: #fff;
  font-size: 1.6rem;
  line-height: 1;
  padding-left: 5px; /* optically centre the triangle */
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.reel-play:hover { background: rgba(0,0,0,0.75); }

.reel-status {
  padding: 40px 24px;
  text-align: center;
  color: var(--text-muted, #8b95a5);
}

.reel-status[hidden] { display: none; }

.reel-sentinel { height: 1px; }

/* ------------------------------------------------------- mobile layout --- */
/* Vertical reel: the clip gets the free space, the caption is a solid block
   BELOW it. Nothing is drawn over the video, so the teaser's burned-in
   captions stay the only text inside the frame. */

@media (max-width: 899px) {
  /* ONE SCROLLER ON THE PAGE, AND IT IS THE STAGE.
     The stage is sized to fill the viewport below the chrome, but the consent
     note and the site footer still sat underneath it, so the DOCUMENT was
     scrollable by ~360px on top of the reel's own scrolling (measured on dev,
     390x844). Two scrollers stacked like that is what let a swipe drag the
     header out of view, and `overscroll-behavior-y: contain` on the stage then
     stopped the reverse swipe from ever scrolling the document back — the user
     was stuck without navigation until a refresh.

     Locking <body> removes the second scroller entirely: the header is pinned
     by construction, not by sticky, and every vertical gesture belongs to the
     reel. `overflow: hidden` on <body> propagates to the viewport (<html>'s
     overflow is `visible`), so this genuinely stops document scrolling rather
     than just clipping.

     The footer goes with it — it is unreachable once the document cannot
     scroll, and leaving it in the flow would only restore the extra scroll
     height. That matches the full-screen reel surfaces this page imitates;
     every footer destination is also in the header's hamburger drawer, which
     is now always on screen. Desktop is untouched: it keeps document scrolling
     and its footer. */
  body.reel-page {
    /* vh before dvh: 100vh on a phone is the LARGE viewport (URL bar hidden),
       so a vh-only lock is taller than what is actually on screen. */
    height: 100vh;  /* fallback: no dvh */
    height: 100dvh; /* tracks the URL bar */
    overflow: hidden;
    /* FLEX COLUMN: the stage's height is what the chrome LEAVES, by layout,
       not by measurement. The previous `calc(100dvh - var(--reel-chrome))`
       could only ever be as fresh as its last measurement, and the chrome is
       alive — the market ticker and the breaking banner appear/disappear on
       their own timers. Every mismatch window left the body's content a few
       px taller than the body, which gave the (overflow:hidden, but still
       PROGRAMMATICALLY scrollable) body a scroll range; the auto-advance
       scroll then shifted the whole page up under the header. Measured on dev
       2026-08-08 at 390x844: body.scrollTop = 24 after one auto-advance, top
       banner clipped by exactly those 24px. With flex, chrome + stage always
       sum to exactly 100dvh, so the body NEVER has scroll range. */
    display: flex;
    flex-direction: column;
  }

  /* The flex chain from body to the stage. min-height:0 at every level or the
     stage's content height wins and the column overflows anyway. */
  body.reel-page .reel-main {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
  }

  body.reel-page .site-consent-note,
  body.reel-page .site-footer { display: none; }

  /* Gone from the phone entirely. Shrinking it (112px -> 61px on 2026-08-08)
     was not enough: every pixel above the stage comes straight out of the clip,
     because .reel-stage is sized as viewport-minus-chrome. Removed from the
     LAYOUT, not from the DOM — .reel-title is this page's only <h1>, so it is
     clipped sr-only style instead of display:none, keeping it for screen
     readers and for the document outline. syncChromeHeight() measures the
     stage's real offset, so --reel-chrome drops on its own and the card grows
     by exactly what the block used to occupy. Desktop keeps the block: it has
     the vertical room, and there the h1 is the page's visible title. */
  .reel-intro {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .reel-stage {
    /* One card == one visible screen — as the flex remainder, NOT as
       `calc(100dvh - var(--reel-chrome))`. The chrome above the stage is
       alive (market ticker, breaking banner, safe-area insets, intro re-wrap
       at large text sizes), so any measured figure is stale the moment one of
       them toggles. Two generations of this rule proved it: the 118px literal
       was wrong as soon as the ticker resolved, and the JS-published
       --reel-chrome was wrong in the window between the chrome changing and
       the ResizeObserver callback landing — in that window body content
       exceeded 100dvh, the body gained programmatic scroll range, and the
       auto-advance scroll dragged the header partly off screen (dev,
       2026-08-08, 390x844: clipped by 24px). The flex chain from body cannot
       go stale: chrome + stage sum to the viewport by construction. */
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
  }

  .reel-item {
    height: 100%;
    width: 100%;
    display: flex;
    flex-direction: column;
  }

  /* The blurred backdrop stays behind the clip only, not behind the caption. */
  .reel-backdrop { bottom: auto; height: 100%; }

  .reel-video {
    width: 100%;
    /* Shrink-only, never grow. Previously flex:1 1 auto let the clip absorb ALL
       free space, so a 9:16 teaser ate ~65% of the viewport and squeezed the
       caption down to whatever was left. Now the clip is capped and the caption
       takes the remainder. */
    width: 100%;
    /* The clip takes everything the caption does not. Now that the caption is
       just category · date + headline it is small AND content-sized, so this
       needs no cap: card height - caption height always fits by construction.
       (The earlier 62% cap existed to defend a caption that carried an excerpt
       and a CTA button and could outgrow the card. That pressure is gone, and
       keeping the cap would only leave dead space under the panel.)
       Deliberately NOT a viewport unit — see the .reel-stage note: a vh/dvh
       figure silently breaks whenever the chrome above the stage changes. */
    flex: 1 1 auto;
    min-height: 0; /* without this the 9:16 intrinsic size wins and overflows */
    height: auto;
    object-fit: contain; /* never crop burned-in captions */
  }

  .reel-page main a.reel-item .reel-caption {
    position: relative;
    /* Grows into whatever the capped clip leaves behind, so the free space ends
       up inside the opaque panel rather than as dead backdrop under it. The
       min-height is the floor that guarantees headline + summary + CTA always
       have somewhere to sit. */
    /* Content-sized: category · date + headline and nothing else. The clip
       flexes into whatever is left, so the two always sum to exactly one card. */
    flex: 0 0 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* NOT align-items:flex-start. That shrink-to-fits every child to its
       max-content width, so a long headline rendered as one over-wide line and
       got clipped by the panel's overflow — "…Trump-Backed President Ta" with
       the rest of the word gone. Stretch is the default; children must span the
       panel so the text wraps inside it. */
    align-items: stretch;
    /* THE HOME INDICATOR EATS THE LAST LINE.
       This panel is pinned to the exact bottom edge of the viewport, and the
       headline is its last element — so on any real phone the iPhone home
       indicator or the Android gesture/navigation bar is drawn straight over
       the final line of the title. That is the "headline cut off at the
       bottom" Nicolas kept screenshotting, and it is invisible to every
       emulator and every automated check, which is why measuring
       scrollHeight vs clientHeight kept coming back clean: nothing is
       CLIPPED, it is COVERED.

       The rest of the site already solves this everywhere it pins something
       to the bottom — scroll-reel.css was the one stylesheet that never
       adopted the convention. max() of both sources because neither alone is
       enough: on the Android WebView env(safe-area-inset-*) reports only
       display cutouts, not the system bars, so the native code publishes the
       real figure as --ubh-inset-bottom (see the ROOT-CAUSE NOTE in
       style.css, 2026-07-07 Samsung regression). Both resolve to 0 on
       desktop, so this adds nothing there. */
    /* ...and a HARD FLOOR under all of it, because on this page neither of
       those two sources is ever non-zero.

       env(safe-area-inset-*) only resolves to a real number when the document
       opts in with `viewport-fit=cover`, and this site's viewport meta is
       plain `width=device-width, initial-scale=1.0`. Nothing anywhere in the
       repo assigns --ubh-inset-bottom either — it is consumed in eleven
       places and set in none. So this padding was 14px flat on every browser
       that has ever loaded the page, and the "safe area" fix above it was a
       no-op the day it shipped.

       14px is not enough clearance for a panel pinned to the exact bottom of
       the viewport. Any browser bottom chrome that OVERLAYS the page rather
       than shrinking it — the Telegram in-app browser's toolbar, Safari's
       bottom bar, an Android gesture bar — takes its pixels out of the last
       lines of the headline, and because the reel locks the document and
       snaps in whole-card increments there is NO WAY TO SCROLL THEM INTO
       VIEW. The text is simply gone.

       Measured on dev, Colombia card, 390x844, headline bottom at 830 with
       the old padding, simulating a bar the page cannot see:

         bar 34px -> lines 1-2 visible, line 3 gone
         bar 44px -> line 1 only     ("US Pledges $1 Billion in Security Aid to")
         bar 54px -> line 1 only
         bar 64px -> NOTHING visible

       The 44-54px rows are character-for-character what Nicolas screenshotted,
       and the 64px row is the "title completely missing" report — same cause,
       shorter headline. So: reserve the space unconditionally. The clip is
       `flex: 1 1 auto` and pays for it by shrinking, which is the trade the
       feature wants — a headline that cannot be read is worth less than 42px
       of video. Desktop is unaffected; this rule is inside the mobile query. */
    padding: 12px 16px calc(14px + max(
      env(safe-area-inset-bottom, 0px),
      var(--ubh-inset-bottom, 0px),
      var(--reel-bottom-chrome-floor, 56px)
    ));
    /* Solid, opaque panel — this is what guarantees no collision with the
       video's own text. Theme tokens so light mode works too. */
    background: var(--surface-raised, var(--bg, #1e1e22));
    border-top: 1px solid var(--border, #232a35);
    color: var(--text, #e8e8ed);
    text-shadow: none;
  }

  .reel-page main a.reel-item .reel-meta { color: var(--text-muted, #8b95a5); }

  /* MOBILE CAPTION = category · date + headline. Nothing else.
     The excerpt and the "Read the full article" link are DELIBERATELY hidden
     here (Nicolas 2026-08-08): the whole card is already the article link, so
     tapping the video or the headline navigates. They were redundant weight
     that ate the vertical space the clip needed. Desktop keeps both — it has a
     full text column beside the video and no such pressure.
     Hidden in CSS rather than skipped in the DOM so the breakpoint alone
     decides, with no rebuild on resize/rotate, and the text stays available to
     assistive tech and to the card's aria-label. */
  .reel-page main a.reel-item .reel-summary,
  .reel-page main a.reel-item .reel-cta {
    display: none;
  }

  /* The shared chrome's push-prompt bell is position:fixed bottom-right at
     z-index 300. On every other page it floats over a scrolling feed; here the
     caption panel is pinned to the bottom of the card, so it landed directly on
     the headline and hid a word ("…Trump-Backed President Ta" — "kes" was
     underneath it). Lift it clear of the panel. Scoped to .reel-page so the
     bell is untouched everywhere else, and it still floats over the clip, which
     is exactly where the mute toggle already lives. */
  .reel-page .push-banner-float {
    bottom: calc(var(--reel-caption-h, 132px) + 16px);
  }

  /* NO LINE CLAMP. The headline is the last element in the panel and the whole
     point of the card, so it must render in full.

     The 3-line clamp that used to live here truncated any longer headline
     mid-sentence with no ellipsis visible inside the opaque panel — measured
     on dev 2026-08-08 at 390x844, card 1 ("Commerce Department Draft Rule
     Would Drop Census Questions on Citizenship"): scrollHeight 84px against
     clientHeight 63px, i.e. the 4th line was cut off entirely. That is the
     "title cut off at the bottom" Nicolas screenshotted.

     Removing the clamp cannot overflow the card: .reel-caption is
     `flex: 0 0 auto` (content-sized) and .reel-video is `flex: 1 1 auto`
     with `min-height: 0`, so a taller headline is paid for by the clip
     shrinking, not by the panel spilling past the card. */
  .reel-headline {
    font-size: 1.05rem;
    margin-bottom: 0;
    /* Explicitly unset: the base rule at the top of this file sets these for
       the shared card style, and a -webkit-box display would re-clamp. */
    display: block;
    -webkit-line-clamp: none;
    line-clamp: none;
    overflow: visible;
  }
}

/* ------------------------------------------------------ desktop layout --- */
/* Centered stage: capped 9:16 player + readable text column beside it. */

@media (min-width: 900px) {
  .reel-stage {
    height: calc(100vh - 156px);
    min-height: 520px;
    /* FULL VIEWPORT WIDTH — deliberately not a centered 1200px box.
       The stage is the scroll container: `overflow-y:auto` +
       `overscroll-behavior-y:contain` means a wheel event only scrolls the
       reel if it lands INSIDE this element. When the stage was capped at
       1200px and centered, a 1440px viewport left ~120px of page background
       down each side; a wheel there hit <body>, which has nothing to scroll,
       so the reel sat dead and only the browser scrollbar worked (reported by
       Nicolas 2026-08-08). Widening the container to the viewport makes every
       X position a live scroll target. The 1200px reading width is preserved
       by capping .reel-item instead, so the layout is visually unchanged. */
    max-width: none;
    margin: 0;
  }

  .reel-item {
    height: 100%;
    width: 100%;
    /* The reading-width cap the stage used to own. Auto margins keep the
       player centered while the scrollable area stays full-bleed. */
    max-width: 1200px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
    align-items: center;
    gap: 44px;
    padding: 32px 48px;
    box-sizing: border-box;
  }

  .reel-video {
    width: 100%;
    aspect-ratio: 9 / 16;
    max-height: calc(100vh - 246px);
    margin: 0 auto;
    object-fit: contain;
    border-radius: 14px;
    box-shadow: 0 18px 48px rgba(0,0,0,0.55);
  }

  .reel-item::after {
    /* Side scrim only: the caption lives in its own column outside the video,
       this just darkens the blurred backdrop so white text stays readable. */
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(to right, rgba(0,0,0,0.35), rgba(0,0,0,0.55));
  }

  .reel-page main a.reel-item .reel-caption {
    color: #fff;
    max-width: 620px;
    text-shadow: 0 1px 8px rgba(0,0,0,0.55);
  }

  .reel-headline { font-size: 1.9rem; }
  .reel-summary {
    font-size: 1rem;
    display: -webkit-box;
    -webkit-line-clamp: 5;
    line-clamp: 5;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .reel-page main a.reel-item .reel-cta {
    /* NOT var(--accent): that token is near-white in dark theme (#e8e8ed),
       which would put white text on a white pill. The caption always sits on
       the darkened backdrop, so a fixed brand-red pill is readable in both
       themes. */
    padding: 10px 18px;
    border-radius: 999px;
    background: var(--red, #ef4444);
    color: #fff;
    font-size: 0.95rem;
    text-shadow: none;
  }

  .reel-item:hover .reel-cta { filter: brightness(1.1); }

  /* Centre it over the CLIP, not over the whole card: the desktop item is a
     two-column grid and its midpoint falls in the text column. 48px padding +
     half of the 360px video column. */
  .reel-play { left: 228px; }
}

/* Very wide screens: keep the text column from stretching to a hard-to-read
   line length. */
@media (min-width: 1500px) {
  .reel-item { grid-template-columns: 400px minmax(0, 1fr); gap: 60px; }
}

@media (prefers-reduced-motion: reduce) {
  .reel-stage { scroll-behavior: auto; }
  .reel-backdrop { filter: blur(30px) brightness(0.45); transform: none; }
}
