  :root {
    --bg: #f7f8fa;
    --card-bg: #ffffff;
    --card-border: #e3e5e8;
    --text: #1f2328;
    --muted: #6b7280;
    --accent: #2f6fed;
    --header-bg: #ffffff;
  }
  :root[data-theme="dark"] {
    --bg: #17191c;
    --card-bg: #1f2226;
    --card-border: #2e3136;
    --text: #e5e7eb;
    --muted: #9aa1ab;
    --accent: #5b93ff;
    --header-bg: #1a1c1f;
  }
  * { box-sizing: border-box; }
  /* Reserves the vertical scrollbar's gutter permanently, so toggling
     overflow-y on <html> to lock scrolling for a beat (see lockScrollFor()
     in the script) never changes the viewport's effective width -- without
     this, the scrollbar disappearing/reappearing each time a lock
     engages/releases would re-trigger the exact "100vw content shifts"
     issue overflow-x:hidden below was already added to fix. */
  html { scrollbar-gutter: stable; }
  html { overflow-x: hidden; }
  body {
    margin: 0;
    /* `hidden` on one axis forces the other axis to compute to `auto`,
       which made <body> a second vertical scroll container alongside the
       root page for long reader views. `clip` prevents horizontal overflow
       without creating that extra scrollbar. */
    overflow-x: clip;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    background: var(--bg);
    color: var(--text);
  }
  header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 20px;
    /* Floats over the hero photo, so it needs to stay legible regardless of
       image content -- a frosted dark bar instead of the theme's normal
       card background, which would clash with (or vanish against) a photo. */
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 10;
    background: rgba(10, 10, 12, 0.28);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border-bottom: none;
    /* Driven by a scroll-progress custom property (see layoutHeroPhoto()),
       same reasoning as .hero-photo-tint's opacity -- deliberately
       untransitioned since JS updates it every scroll frame and a
       transition here would lag behind rather than track it. The page-0
       title-page has no header of its own, so it just stays hidden there
       and fades in exactly as the photo does. */
    opacity: var(--header-opacity, 1);
  }
  /* "Get out of the way for the photo": hovering the location credit clears
     the header (fixed top bar) so the photo can be viewed at full
     brightness, on top of the existing scale+brightness hover treatment on
     .hero-photo itself below. #heroIntro used to be listed here too, but
     now that layoutHeroPhoto() also writes #heroIntro's own opacity
     directly (see isLocationHovered there), a CSS rule targeting the same
     element/property would just get silently overwritten by the next
     scroll-driven frame -- header is still safe here since JS never
     touches its opacity (that's --header-opacity, a different property). */
  body:has(.hero-location:hover) header {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.4s ease;
  }
  /* The "parallax" effect: this is the SAME photo as the hero, not a copy --
     a single fixed element (#heroPhoto below) that starts covering the
     full viewport and, as you scroll from hero to intro, is repositioned
     and resized every scroll frame to exactly match #introPhotoSlot's live
     on-screen rect (a real, empty, reserved box at the top of the intro
     text). Shrinking from a tall full-bleed box down to a short wide one
     also means background-size:cover naturally crops far less of the
     photo at rest than it did full-bleed, reading as a "zoom out" for
     free -- no separate zoom animation needed, just the box shape
     changing. See layoutHeroPhoto() in the script.

     top/left/width/height are genuinely animated here (not a GPU-friendlier
     `transform: scale()`) -- that was tried, since resizing this box every
     scroll frame is real repaint cost, but scale() distorts the image: the
     viewport and #introPhotoSlot have very different aspect ratios, so
     scaleX != scaleY, and background-size:cover -- which only ever
     computes correctly against this element's actual layout size -- has no
     way to know the box has been squished non-uniformly by a transform
     after the fact. The photo ended up visibly stretched, permanently, not
     just mid-transition. Real width/height keeps `cover` recalculating
     correctly at every step, which is worth the extra repaint cost; the
     other, non-visual fixes alongside this one (batching every geometry
     read before any style write, and not double-computing this per frame
     via two separate scroll listeners) are what actually mattered for
     smoothness. */
  .hero-photo {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: 1;
    background-image: url('images/home-hero.jpg'), linear-gradient(135deg, #20242b, #2f3a4d);
    background-size: cover;
    background-position: center;
    pointer-events: none;
    transition: transform 0.8s ease;
  }
  /* Hover-zoom on the location credit applies via transform specifically
     because layoutHeroPhoto() never touches it -- it only ever sets
     top/left/width/height/border-radius inline, so transform stays free
     for a CSS transition without any fighting. No filter/brightness boost
     here (there used to be one, stacked on top of PHOTO_TINT_REST_MIN's
     own reveal) -- two rounds of feedback landed on wanting the normal,
     unboosted photo once the tint clears, not an artificially brightened
     one; stacking both read as blown-out/harsh on an already-bright photo. */
  body:has(.hero-location:hover) .hero-photo { transform: scale(1.06); }
  .hero-photo-tint {
    position: absolute;
    inset: 0;
    background: linear-gradient(rgba(8,9,11,0.62), rgba(8,9,11,0.72));
    /* Driven by a scroll-progress custom property, updated every scroll
       frame by layoutHeroPhoto() -- deliberately untransitioned at rest. A
       CSS transition here would restart on every frame's new value and
       never catch up, lagging visibly behind the actual scroll position.
       The hover-triggered drop to HERO_PHOTO_HOVER_TINT is the one
       exception -- it isn't a per-frame value, just a single change on
       mouseenter/mouseleave, so it can safely transition without that
       problem. See .tint-hover-transition below and the mouseenter/
       mouseleave listeners in the script, which add/remove that class
       specifically around the hover toggle rather than leaving a
       transition on permanently. */
    opacity: var(--tint, 1);
  }
  .tint-hover-transition { transition: opacity 0.6s ease; }
  /* Same reasoning again, for header's one-time reveal once
     titleEntranceRevealed flips true (see the script) -- header's own
     opacity is otherwise deliberately untransitioned, since --header-opacity
     is rewritten every scroll frame and a permanent transition would lag
     behind actual scroll position. The 0.3s delay is deliberate too, not
     just the easing: without it the header appeared the instant the title+
     stats entrance settled, reading as another beat in the same sequence
     rather than a distinct, separately-noticed reveal. */
  .header-reveal-transition { transition: opacity 0.6s ease 0.3s; }
  /* Same reasoning as .tint-hover-transition above, for #heroIntro's own
     hover-declutter opacity (see isLocationHovered in the script) --
     opacity is otherwise deliberately untransitioned here too, since it's
     rewritten every scroll frame and a permanent transition would lag
     behind actual scroll position. */
  .hero-intro-hover-transition { transition: opacity 0.4s ease; }
  .header-controls {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .theme-toggle {
    width: 34px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid rgba(255,255,255,0.3);
    border-radius: 6px;
    background: transparent;
    color: rgba(255,255,255,0.75);
    cursor: pointer;
    font: inherit;
    font-size: 0.9rem;
    line-height: 1;
    transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
  }
  .theme-toggle:hover {
    background: rgba(255,255,255,0.12);
    border-color: #fff;
    color: #fff;
  }
  .theme-toggle:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
  header .lang-btn { color: rgba(255,255,255,0.75); background: transparent; transition: background 0.25s ease, color 0.25s ease, opacity 0.25s ease; }
  header .lang-btn.active { background: rgba(255,255,255,0.22); color: #fff; }
  header .lang-btn:not(.active):hover { background: rgba(255,255,255,0.12); color: #fff; }
  /* Hovering the inactive button previews the swap by dimming the active
     one down to look inactive too, instead of it staying fully lit while
     you're clearly looking at the other option. */
  header .lang-switch:has(.lang-btn:not(.active):hover) .lang-btn.active {
    background: transparent;
    color: rgba(255,255,255,0.75);
  }
  header .lang-switch { border-color: rgba(255,255,255,0.3); }
  /* .cta-skip is an <a>, which flex align-items:center doesn't center quite
     the same as the lang-switch buttons (measured 6px low against the
     header's own vertical center) -- nudged to match exactly. */
  header .cta-skip { color: #fff; border-color: rgba(255,255,255,0.35); margin-top: -3px; }
  header .cta-skip:hover { border-color: #fff; }
  :root[data-theme="light"] header {
    background: rgba(247,248,250,0.78);
    border-bottom: 1px solid rgba(31,35,40,0.1);
  }
  :root[data-theme="light"] header .theme-toggle {
    border-color: rgba(31,35,40,0.25);
    color: rgba(31,35,40,0.7);
  }
  :root[data-theme="light"] header .theme-toggle:hover {
    background: rgba(31,35,40,0.08);
    border-color: var(--text);
    color: var(--text);
  }
  :root[data-theme="light"] header .theme-toggle:focus-visible { outline-color: var(--text); }
  :root[data-theme="light"] header .lang-switch { border-color: rgba(31,35,40,0.22); }
  :root[data-theme="light"] header .lang-btn { color: rgba(31,35,40,0.65); }
  :root[data-theme="light"] header .lang-btn.active {
    background: rgba(31,35,40,0.14);
    color: var(--text);
  }
  :root[data-theme="light"] header .lang-btn:not(.active):hover {
    background: rgba(31,35,40,0.08);
    color: var(--text);
  }
  :root[data-theme="light"] header .lang-switch:has(.lang-btn:not(.active):hover) .lang-btn.active {
    background: transparent;
    color: rgba(31,35,40,0.65);
  }
  :root[data-theme="light"] header .cta-skip {
    color: var(--text);
    border-color: rgba(31,35,40,0.28);
  }
  :root[data-theme="light"] header .cta-skip:hover {
    background: rgba(31,35,40,0.08);
    border-color: var(--text);
  }
  :root header.insights-active {
    background: transparent;
    border-bottom-color: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  /* The page 1–3 header remains visually present above page 4 for its
     theme/language controls, but its full-width transparent box must not
     intercept clicks intended for the reader navigation underneath. */
  body > header.insights-active { pointer-events: none; }
  body > header.insights-active .header-controls,
  body > header.insights-active #menuToggle { pointer-events: auto; }
  /* Page 0 -- a Fantasy-style title-only opener that precedes the photo.
     Deliberately hardcoded dark (not the theme's --bg/--text, which flip
     for light-mode system preference) since this is meant to read as an
     always-dark first impression, same reasoning as .hero below. */
  .title-page {
    position: relative;
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0 64px;
    background: #0b0c0e;
    color: #fff;
  }
  .title-page h1 {
    margin: 0 0 32px;
    font-size: 3.4rem;
    font-weight: 700;
    line-height: 1.15;
    color: #fff;
    text-shadow: 0 2px 20px rgba(0,0,0,0.4);
    /* Directional (Y-only) blur -- a plain CSS blur() is symmetric and
       can't do this, so the actual blur amount is an SVG feGaussianBlur
       with independent x/y stdDeviation, animated via GSAP (see script).
       0 on both axes at rest is a no-op filter, same as no filter at all. */
    filter: url(#titleVerticalBlur);
  }
  /* Each line locked to a single row so it can't wrap into a 2nd line at
     the reveal's wide starting letter-spacing and then un-wrap back to 1
     as it settles -- that line-count change is a much more jarring jump
     than the width simply changing (which is fine here: centered, so the
     block just grows/shrinks evenly from its own center, never
     repositions). Reverts to normal wrapping on mobile, where there isn't
     room to guarantee this and forcing it would just overflow instead. */
  .title-line { white-space: nowrap; }
  .hero {
    position: relative;
    height: 100vh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0 24px;
    color: #fff;
  }
  .title-page .stats {
    border-color: rgba(255,255,255,0.25);
    justify-content: center;
  }
  .title-page .stat-num { color: #fff; }
  .title-page .stat-label { color: rgba(255,255,255,0.7); }
  /* Shared anchor so .title-challenges can sit exactly over .stats (same
     top-left origin) for the crossfade "morph" between them -- see the
     onComplete chain in applyLang(). */
  .title-stats-wrap { position: relative; }
  /* Only reachable by scrolling back up past the title once it's fully
     wound back (see pageOneStage in the script) -- a small easter-egg
     centered on the page, in the same spot the title/stats occupied.
     Brand-red hover glow matches the treatment already used for this same
     link elsewhere on the site. */
  .title-brand {
    position: absolute;
    z-index: 2;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
    text-align: center;
  }
  /* Same entrance language as the title/stats: the link (top line) drops
     in from above, the subtitle (bottom line) rises in from below -- both
     driven by setPageOneStage() in the script, same as pageTitleEl/statsEl
     above. Initial opacity:0 + offset here is the "hidden" resting look;
     GSAP takes over the transform/opacity once a reveal actually starts. */
  .title-brand-link {
    display: inline-block;
    color: rgba(255,255,255,0.75);
    font-size: 1.7rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    text-decoration: none;
    opacity: 0;
    transform: translateY(-40px);
    transition: color 0.3s ease, text-shadow 0.3s ease;
  }
  .title-brand-link:hover {
    color: #f90706;
    text-shadow: 0 0 6px rgba(249,7,6,0.85), 0 0 16px rgba(249,7,6,0.55), 0 0 28px rgba(249,7,6,0.3);
  }
  .title-brand-sub {
    margin-top: 8px;
    font-size: 0.85rem;
    font-weight: 500;
    letter-spacing: 0.04em;
    color: rgba(255,255,255,0.5);
    opacity: 0;
    transform: translateY(24px);
  }
  .title-challenges {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    opacity: 0;
    max-width: 520px;
  }
  .title-challenges p {
    font-size: 1rem;
    line-height: 1.6;
    margin: 0 0 14px;
    color: rgba(255,255,255,0.92);
  }
  /* Numbered-editorial style (01/02/03) instead of plain default bullets --
     a muted small index number beside each bold word, generously spaced. */
  .title-challenges ul {
    counter-reset: challenge;
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .title-challenges li {
    position: relative;
    counter-increment: challenge;
    padding-left: 34px;
    font-size: 1.2rem;
    font-weight: 700;
    color: #fff;
    letter-spacing: 0.01em;
  }
  .title-challenges li::before {
    content: counter(challenge, decimal-leading-zero);
    position: absolute;
    left: 0;
    top: 0.2em;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    color: rgba(255,255,255,0.45);
  }
  /* The photo page's own teaser copy -- the "3 mental challenges"
     conclusion, moved here from the title page. Full-bleed band (same
     100vw/margin-left:-50vw full-bleed trick .intro::before/::after use)
     rather than a narrow centered card, so its translucent dark + blur
     background spans the whole section edge to edge -- .hero's own
     overflow:hidden clips it cleanly, same as #heroPhoto already relies on.
     The fluid horizontal padding below keeps the actual text/stats content
     from stretching edge-to-edge on wide viewports while the background
     itself stays full width. */
  .hero-intro {
    /* `fixed`, not `absolute` -- the same fix #heroTagline/#heroLocation
       needed. `absolute` centers this within .hero's own box, which is a
       normal in-flow section that scrolls with the page -- so even with
       opacity correctly held at 1 the whole block still visibly scrolls by
       at a constant rate the entire time, indistinguishable from not
       holding at all. #heroPhoto holds because it's fixed (glued to the
       viewport, repositioned by JS); this needs to be glued the same way
       for its own hold (see HERO_INTRO_HOLD_FRAC in the script) to read as
       an actual pause rather than just "not fading yet". */
    position: fixed;
    z-index: 2;
    top: 50%;
    left: 50%;
    width: 100vw;
    margin-left: -50vw;
    transform: translateY(-50%);
    /* Centered, not left-aligned -- the intro sentence used to be the only
       content here and read poorly centered past a line or two, but now
       that the conclusions below it are a centered stat-style row too
       (same as .title-page), a left-aligned sentence sitting above a
       centered row just looked like two mismatched blocks stacked on top
       of each other. */
    text-align: center;
    background: rgba(8,9,11,0.4);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    /* Wider column (was 720px) and more vertical padding (was 40px) --
       made bigger/more explicit at the user's request, and 5 conclusions
       need more breathing room in a row than the previous 3 did anyway. */
    padding: 56px max(40px, calc((100% - 960px) / 2));
    filter: url(#heroIntroVerticalBlur);
  }
  .hero-intro .home-intro {
    font-size: 1.15rem;
    font-weight: 600;
    margin: 0 0 14px;
    color: #fff;
  }
  .hero-intro p {
    font-size: 1.2rem;
    line-height: 1.6;
    margin: 0 0 28px;
    color: rgba(255,255,255,0.92);
  }
  .hero-intro p:last-child { margin-bottom: 0; }
  /* The 5 conclusions, styled like the title page's own .stats (same
     bordered row) rather than the numbered list this used to be --
     consistency with the stats block just above it in the narrative, and
     with a real number ("01".."05") in the stat-num slot again like the
     original numbered-list treatment had. Sizing deliberately inverts
     page 1's own stats (small muted number, large bold label) -- the
     number is a subtle index, not the point, the word next to it is --
     and, unlike page 1's centered stats, left-aligned within each item:
     the longest label ("Self-control" originally,
     now shorter but this still generalizes) can wrap to more than one
     line, and a centered multi-line wrap reads as a ragged "pyramid"
     rather than a clean flush-left block of text. */
  .hero-intro .stats { border-color: rgba(255,255,255,0.25); justify-content: center; margin: 0; row-gap: 24px; }
  .hero-intro .stat { text-align: left; }
  .hero-intro .stat-num { color: rgba(255,255,255,0.4); font-size: 0.8rem; }
  .hero-intro .stat-label { color: #fff; font-size: 1.3rem; font-weight: 700; }
  /* Centered horizontally, matching the now fully-centered title/stats/
     brand-link content above it. Vertical offset is a small FIXED pixel
     amount rather than a percentage of viewport height specifically
     because a height-percentage (e.g. bottom:33%) only cleared the
     vertically-centered content above it by ~3px in testing -- flips into
     a real overlap on any shorter viewport or taller card content. A
     fixed offset stays clear of vertically-centered content regardless of
     viewport height, since centered content by definition leaves the
     bottom margin free. */
  .scroll-cue-arrow {
    font-size: 1.1rem;
    line-height: 1;
    /* Independent of whatever the containing .stage-cue button's own
       opacity is doing (e.g. #revealBrandCue's own scroll-driven
       visibility) -- this is a *second*, separate opacity layer specifically
       for the scroll-idle show/hide (see the 'scroll' listener in the
       script): fades out immediately the moment any scroll happens, fades
       back in only once scrolling has actually stopped for a beat. Living
       on this inner span rather than the outer button means the two
       controls (stage-relevance vs. scroll-idle) never fight over the same
       element's opacity -- final visibility is just the product of both,
       same as nesting always composes in CSS. */
    transition: opacity 0.25s ease;
  }
  .scroll-cue-arrow.scroll-hiding { opacity: 0; }
  @keyframes scrollCueBounce {
    0%, 100% { transform: translateX(-50%) translateY(0); }
    50% { transform: translateX(-50%) translateY(6px); }
  }
  /* Plain clickable arrow buttons -- no "SCROLL" text label, just a
     bouncing arrow, always horizontally centered and always anchored to
     the very top or very bottom of the page (never floating inline within
     a content block). Exactly two on the title page: #revealBrandCue (top,
     reveals the brand-link easter egg, a crossfade toggle -- see
     setPageOneStage() in the script) and #titleScrollCue (bottom,
     stage-aware: advances brand-link -> title+stats first, then scrolls on
     to the hero section -- see its click handler below). Also used for the
     hero->intro cue (#scrollCue), which used to say "SCROLL". */
  .stage-cue {
    background: none;
    border: none;
    /* Slightly larger than the visible arrow glyph itself -- a bigger
       click/tap target without the arrow looking any bigger. The
       top/bottom offsets below are each reduced by this padding's own
       size so the glyph still lands in the exact same visual spot; only
       the invisible hit area around it grows. */
    padding: 8px 14px;
    color: rgba(255,255,255,0.8);
    cursor: pointer;
    animation: scrollCueBounce 2s ease-in-out infinite;
    transition: opacity 0.25s ease;
  }
  .stage-cue .scroll-cue-arrow { display: block; }
  .stage-cue-up,
  .stage-cue-down {
    position: absolute;
    z-index: 2;
    left: 50%;
    transform: translateX(-50%);
  }
  /* Above header's own z-index:10 -- #revealBrandCue sits inside the
     header's own 0-68px vertical span, and header stays visible at
     that exact spot even at rest on page 1 (see headerOpacity's
     Math.max(titleProgress, titleEntranceRevealed) in the script) rather
     than only while actively scrolled away from it like it used to. Once
     header could show while resting at the very top too, it started
     rendering on top of (and swallowing every click on) this cue, both
     sharing z-index:2 with everything else here -- opacity/pointer-events
     were correctly '1'/'auto' the whole time, the arrow was just
     physically hidden behind the header bar. */
  .stage-cue-up { z-index: 11; }
  .stage-cue-down { bottom: 52px; }
  #titleScrollCue {
    /* Mirrors #revealBrandCue's 14px top offset. Both arrows have the
       same 8px inner padding and opposite 6px bounce, so their visible
       glyphs keep the same average distance from their viewport edge. */
    bottom: 14px;
    /* Prevent an initial flash before the delayed JavaScript reveal. */
    opacity: 0;
    pointer-events: none;
  }
  #titleScrollCue.initial-cue-visible { transition-duration: 0.55s; }
  /* #scrollCue specifically (page 2's down cue) lines up with
     .hero-tagline/.hero-location instead of the default 60px -- those two
     sit at bottom:28px, and the arrow read as floating at a mismatched
     height above that shared baseline rather than sitting on it.
     position:fixed (not the shared .stage-cue-down's absolute), for the
     same reason #heroTagline/#heroLocation are fixed: .hero is a normal
     in-flow section that scrolls away like anything else once you're past
     its arrival, and an absolutely-positioned child drifts right along
     with it -- this used to be absolute too, and stayed visually aligned
     with the tagline/location only because *they* were absolute-in-.hero
     as well back then; once those two became fixed (to genuinely hold
     their position during the hero->intro hold), #scrollCue was left
     behind, still tracking .hero's own shifting box instead of the
     viewport, and drifted away from the baseline it's meant to share. */
  #scrollCue {
    position: fixed;
    bottom: 20px;
    /* Hidden before JavaScript's first layout pass, preventing the page-2
       cue from flashing alongside page 1's own arrow during initial load. */
    opacity: 0;
    pointer-events: none;
  }
  /* Give the delayed transition helper a more deliberate entrance. Its
     exit still uses .stage-cue's shorter 0.25s transition. */
  #scrollCue.transition-cue-visible { transition-duration: 0.55s; }
  .stage-cue-up {
    /* The visible glyph has 8px of button padding above it. At 14px this
       puts its animated average top edge on the same horizontal line as
       the left CTA and right language controls. */
    top: 14px;
    /* Starts hidden -- only ever shown once the user has scrolled away
       from the very top and back again (see hasScrolledAwayFromTop in the
       script); never automatically on page load. */
    opacity: 0;
    animation-name: stageCueBounceUp;
  }
  @media (max-width: 640px) {
    .stage-cue-up {
      /* Matches the global header's roughly 5px smaller phone padding. */
      top: 9px;
    }
    #titleScrollCue {
      bottom: 9px;
    }
  }
  @keyframes stageCueBounceUp {
    0%, 100% { transform: translateX(-50%) translateY(0); }
    50% { transform: translateX(-50%) translateY(-6px); }
  }
  .hero-location {
    position: fixed;
    z-index: 2;
    bottom: 28px;
    right: 28px;
    display: flex;
    align-items: center;
    gap: 6px;
    color: rgba(255,255,255,0.75);
    text-decoration: none;
    font-size: 0.78rem;
    letter-spacing: 0.02em;
  }
  /* Hovering swaps the tagline for the actual URL (crossfaded via CSS
     grid stacking both spans in the same cell), same brand-red glow as
     the .title-brand-link easter egg for a consistent hover language. */
  .hero-tagline {
    position: fixed;
    z-index: 2;
    bottom: 28px;
    left: 28px;
    display: grid;
    color: rgba(255,255,255,0.75);
    font-size: 0.78rem;
    letter-spacing: 0.02em;
    text-decoration: none;
  }
  .hero-tagline span {
    grid-area: 1 / 1;
    transition: opacity 0.25s ease, color 0.25s ease, text-shadow 0.25s ease;
  }
  .hero-tagline-url { opacity: 0; }
  .hero-tagline:hover .hero-tagline-text { opacity: 0; }
  .hero-tagline:hover .hero-tagline-url {
    opacity: 1;
    color: #f90706;
    text-shadow: 0 0 6px rgba(249,7,6,0.85), 0 0 16px rgba(249,7,6,0.55), 0 0 28px rgba(249,7,6,0.3);
  }
  .hero-location span:last-child {
    text-decoration: underline;
    text-decoration-color: transparent;
    transition: text-decoration-color 0.2s ease, color 0.2s ease;
  }
  .hero-location:hover span:last-child {
    text-decoration-color: currentColor;
    color: #fff;
  }
  .hero-location-icon { width: 14px; height: 14px; flex-shrink: 0; }
  .lang-switch {
    display: flex;
    flex-shrink: 0;
    border: 1px solid var(--card-border);
    border-radius: 6px;
    overflow: hidden;
  }
  .lang-btn {
    padding: 6px 14px;
    font-size: 0.8rem;
    font-weight: 600;
    background: var(--card-bg);
    color: var(--muted);
    border: none;
    cursor: pointer;
  }
  .lang-btn.active { background: var(--text); color: var(--bg); }
  .intro {
    position: relative;
    display: block;
    max-width: 960px;
    margin: 0 auto;
    /* Top padding clears the fixed header, which keeps floating over this
       section too (its dark frosted background stops mattering once
       there's no photo behind it, since intro's own bg already shows).
       Keep only a compact closing gutter below the disclaimer; the
       scroll-driven copy reveal explicitly completes at the bottom of the
       document, so it no longer needs artificial viewport-sized space. */
    padding: 84px 24px 20px;
    /* Guarantees at least one full viewport height of scroll room inside
       .intro itself, matching #heroSection's own `height: 100vh` -- the
       hero->intro shrink (photoShapeT, see layoutHeroPhoto()) only
       finishes ramping to 1 once the user has scrolled a full
       #heroSection-height *past* the hero. On a short-content page viewed
       on a tall viewport, .intro's real content (paragraphs + CTA +
       disclaimer) can end up shorter than that, so the document runs out
       of scroll room before the transition ever completes -- #heroPhoto
       gets stuck mid-shrink permanently, oversized and misaligned against
       #introPhotoSlot, reading as the photo overlapping the text below
       it. min-height here ensures that can't happen regardless of
       viewport size or content length. */
    min-height: 100vh;
  }
  /* A flat --bg read as boring next to the photo/title pages -- this gives
     it a soft glow anchored where the photo lands, plus a fine grain for
     tactile depth, both tying it back to the moodier pages above it. Full-
     bleed via the classic negative-margin trick since .intro's own box is
     capped to the 960px text column but this should reach the viewport
     edges. Negative z-index puts them behind .intro's real content
     (non-positioned, so it would otherwise paint above any 0-or-positive-
     z-index sibling) without touching anything else on the page. */
  .intro::before,
  .intro::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 100vw;
    margin-left: -50vw;
    pointer-events: none;
  }
  .intro::before {
    z-index: -2;
    background:
      radial-gradient(ellipse 900px 500px at 50% 0%, rgba(255,255,255,0.06), transparent 60%),
      var(--bg);
  }
  .intro::after {
    z-index: -1;
    opacity: 0.05;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
    background-size: 180px 180px;
  }
  /* Empty on purpose -- reserves the real layout space #heroPhoto lands in.
     Its width naturally matches the text column (.intro's max-width minus
     padding), which is the "fits the text width" part of the effect. */
  .intro-photo-slot {
    width: 100%;
    height: 320px;
    margin: 0 0 40px;
  }
  .stats {
    display: flex;
    gap: 28px;
    flex-wrap: wrap;
    padding: 20px 0;
    margin: 0 0 40px;
    border-top: 1px solid var(--card-border);
    border-bottom: 1px solid var(--card-border);
  }
  .stat-num {
    font-size: 1.7rem;
    font-weight: 700;
    color: var(--text);
    line-height: 1.2;
  }
  .stat-label {
    font-size: 0.82rem;
    color: var(--muted);
  }
  .home-copy p {
    font-size: 1.05rem;
    line-height: 1.75;
    margin: 0 0 22px;
  }
  .home-intro {
    font-size: 1.2rem;
    font-weight: 600;
    /* Same vertical-blur-in language as pageTitleEl/#heroIntro's own
       paragraph -- this was missing entirely before, which is why this
       heading's reveal read as inconsistent with pages 1/2 despite already
       sharing their letter-spacing/opacity/y treatment: the blur-focusing
       part of that shared language simply wasn't here yet. */
    filter: url(#homeIntroVerticalBlur);
  }
  .home-signoff { margin-top: 36px; }
  /* Handwritten-looking signature on the closing name only -- "Warme
     groet,"/"Warm regards," above it stays in the regular body font, same
     as a real letter where the greeting is typed/printed but the name at
     the bottom is signed by hand. */
  .signature {
    font-family: 'Dancing Script', cursive;
    font-size: 1.8rem;
    font-weight: 700;
    line-height: 1;
  }
  .cta {
    display: inline-block;
    margin-top: 12px;
    padding: 7px 14px;
    background: none;
    color: var(--text);
    border: 1px solid var(--text);
    text-decoration: none;
    border-radius: 6px;
    font-weight: 600;
    font-size: 0.8rem;
    transition: background 0.25s ease, color 0.25s ease;
  }
  .cta:hover { background: rgba(128,128,128,0.18); color: var(--text); }
  .cta-skip {
    display: inline-block;
    flex-shrink: 0;
    padding: 7px 14px;
    background: none;
    color: var(--text);
    border: 1px solid var(--card-border);
    border-radius: 6px;
    font-weight: 600;
    font-size: 0.8rem;
    transition: border-color 0.25s ease, opacity 0.25s ease;
  }
  .cta-skip:hover { border-color: var(--text); opacity: 1; }
  .disclaimer {
    margin-top: 48px;
    padding-top: 20px;
    border-top: 1px solid var(--card-border);
    color: var(--muted);
    font-size: 0.68rem;
    line-height: 1.55;
  }
  .disclaimer-title {
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.72rem;
    margin: 0 0 8px;
  }
  .disclaimer p { margin: 0 0 8px; }
  .disclaimer p:last-child { margin-bottom: 0; }
  .insights-page {
    position: relative;
    z-index: 5;
    width: 100%;
    height: 100vh;
    min-height: 620px;
    background: var(--bg);
  }
  .insights-reader-shell {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
    background: var(--bg);
    opacity: 0;
    will-change: opacity;
  }
  .insights-dock-footer {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    padding: 10px 0;
    border-top: 1px solid var(--card-border);
    background: var(--header-bg);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    will-change: opacity;
  }
  .insights-dock-footer-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    max-width: 952px;
    margin: 0 auto;
    padding: 0 20px;
  }
  .insights-newsletter-form {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
  }
  .insights-newsletter-form input {
    flex: 1;
    min-width: 140px;
    max-width: 320px;
    padding: 7px 12px;
    border: 1px solid var(--card-border);
    border-radius: 6px;
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: 0.85rem;
  }
  .insights-newsletter-form button {
    padding: 7px 14px;
    border: 1px solid var(--card-border);
    border-radius: 6px;
    background: none;
    color: var(--text);
    font: inherit;
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
  }
  .insights-footer-link {
    flex-shrink: 0;
    color: var(--muted);
    text-decoration: none;
    font-size: 0.8rem;
    font-weight: 600;
    text-shadow: 0 0 0 transparent;
    transition: color 0.35s ease, text-shadow 0.35s ease;
  }
  .insights-footer-link:hover {
    color: #f90706;
    text-shadow:
      0 0 6px rgba(249,7,6,0.85),
      0 0 16px rgba(249,7,6,0.55),
      0 0 28px rgba(249,7,6,0.3);
  }
  /* Light Home: invert the story pages as well as the long-form section.
     The photograph remains, but a light tint and translucent white text
     band keep black typography readable over it. */
  :root[data-theme="light"] .title-page {
    background: #fff;
    color: #111;
  }
  :root[data-theme="light"] .title-page h1 {
    color: #111;
    text-shadow: none;
  }
  :root[data-theme="light"] .title-page .stats {
    border-color: rgba(17,17,17,0.2);
  }
  :root[data-theme="light"] .title-page .stat-num { color: #111; }
  :root[data-theme="light"] .title-page .stat-label { color: rgba(17,17,17,0.65); }
  :root[data-theme="light"] .title-brand-link { color: rgba(17,17,17,0.75); }
  :root[data-theme="light"] .title-brand-sub { color: rgba(17,17,17,0.55); }
  :root[data-theme="light"] .title-challenges p { color: rgba(17,17,17,0.88); }
  :root[data-theme="light"] .title-challenges li { color: #111; }
  :root[data-theme="light"] .title-challenges li::before { color: rgba(17,17,17,0.45); }
  :root[data-theme="light"] .hero { color: #111; }
  :root[data-theme="light"] .hero-photo {
    filter: contrast(1.08) saturate(0.96);
  }
  :root[data-theme="light"] .hero-photo-tint {
    background: linear-gradient(rgba(255,255,255,0.54), rgba(255,255,255,0.46));
  }
  :root[data-theme="light"] .hero-intro {
    background: rgba(255,255,255,0.72);
  }
  :root[data-theme="light"] .hero-intro .home-intro,
  :root[data-theme="light"] .hero-intro .stat-label { color: #111; }
  :root[data-theme="light"] .hero-intro p { color: rgba(17,17,17,0.88); }
  :root[data-theme="light"] .hero-intro .stats { border-color: rgba(17,17,17,0.22); }
  :root[data-theme="light"] .hero-intro .stat-num { color: rgba(17,17,17,0.48); }
  :root[data-theme="light"] .stage-cue { color: rgba(17,17,17,0.78); }
  :root[data-theme="light"] .hero-location,
  :root[data-theme="light"] .hero-tagline {
    color: rgba(17,17,17,0.72);
    text-shadow: 0 1px 8px rgba(255,255,255,0.9);
  }
  :root[data-theme="light"] .hero-location:hover span:last-child { color: #111; }
  @media (max-width: 640px) {
    header { padding: 14px 16px; gap: 8px; }
    .cta-skip { padding: 6px 10px; font-size: 0.72rem; }
    .intro { padding: 84px 20px 20px; }
    .intro-photo-slot { height: 200px; }
    .insights-dock-footer { padding: 8px 0; }
    .insights-dock-footer-inner { padding: 0 14px; }
    .insights-footer-link { display: none; }
    .insights-newsletter-form { gap: 6px; }
    .insights-newsletter-form input { min-width: 0; padding: 6px 8px; font-size: 0.8rem; }
    .insights-newsletter-form button { padding: 6px 10px; font-size: 0.72rem; }
    .title-page { padding: 0 24px; }
    .title-page h1 { font-size: 2rem; }
    .title-line { white-space: normal; }
    .stats { padding: 14px 0; margin: 0 0 24px; }
    .title-brand-link { font-size: 1.3rem; }
    .hero-intro { padding: 28px 24px; }
    .hero-intro .home-intro { font-size: 1.05rem; }
    .hero-intro p { font-size: 1rem; line-height: 1.55; margin: 0 0 20px; }
    .hero-intro .stat-num { font-size: 0.7rem; }
    .hero-intro .stat-label { font-size: 1.05rem; }
    /* Not enough room on mobile for these alongside the rest of the hero
       without feeling cramped -- display:none rather than just hiding via
       opacity, since JS never touches `display` on these elements, so
       there's nothing to fight/override here. */
    .hero-tagline,
    .hero-location {
      display: none;
    }
  }

  /* Legacy reader styles retained inline while the active page remains standalone. */

  .insights-reader {
    --bg: #f7f8fa;
    --card-bg: #ffffff;
    --card-border: #e3e5e8;
    --text: #1f2328;
    --muted: #6b7280;
    --accent: #343a40;
    --header-bg: rgba(255,255,255,0.92);
    --active-bg: rgba(31,35,40,0.07);
    --card-highlight: rgba(31,35,40,0.018);
    --ambient-glow: rgba(31,35,40,0.045);
    --lang-active-bg: rgba(31,35,40,0.14);
    --lang-hover-bg: rgba(31,35,40,0.08);
    --header-h: 132px;
    --tabs-h: 50px;
    --footer-h: 0px;
    --toc-bar-h: 0px;
    /* Mobile overlay starts just below the fixed language controls rather
       than below the taller reader header, so it intentionally covers the
       search field instead of pushing or following it down. */
    --mobile-menu-top: 58px;
  }
  :root[data-theme="dark"] .insights-reader {
    --bg: #17191c;
    --card-bg: #1f2226;
    --card-border: #2e3136;
    --text: #e5e7eb;
    --muted: #9aa1ab;
    --accent: #e5e7eb;
    --header-bg: rgba(18,20,23,0.9);
    --active-bg: rgba(255,255,255,0.075);
    --card-highlight: rgba(255,255,255,0.018);
    --ambient-glow: rgba(255,255,255,0.045);
    --lang-active-bg: rgba(255,255,255,0.22);
    --lang-hover-bg: rgba(255,255,255,0.12);
  }
  .insights-reader * { box-sizing: border-box; }
  /* Every element below has its own :hover rule that changes color/
     background/border-color/opacity -- this makes all of those fade in and
     back out instead of switching instantly. */
  .insights-reader .home-link, .insights-reader .search-clear, .insights-reader .footer-site-link, .insights-reader .newsletter-form button, .insights-reader .toc-tab, .insights-reader .mobile-menu-site-link, .insights-reader .toc-link, .insights-reader .video-title, .insights-reader .tag-pill, .insights-reader .tag-count, .insights-reader .glossary-term, .insights-reader .quote-context, .insights-reader .admin-export-btn, .insights-reader .admin-unhide-btn, .insights-reader .hide-btn, .insights-reader .internal-ref, .insights-reader .chart-thumb img, .insights-reader .msg-image img {
    transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
  }
  .insights-reader, .insights-reader {
    margin: 0;
    max-width: 100%;
    /* clip (not hidden) avoids a CSS spec quirk where setting only
       overflow-x forces overflow-y to auto, which would turn html/body
       into their own scroll container and break position:sticky. */
    overflow-x: clip;
  }
  .insights-reader {
    padding-bottom: var(--footer-h);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    background:
      radial-gradient(ellipse 900px 520px at 50% 0%, var(--ambient-glow), transparent 68%),
      var(--bg);
    background-attachment: fixed;
    color: var(--text);
  }
  .insights-reader header {
    position: sticky;
    top: 0;
    background: var(--header-bg);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    padding: 20px 0 14px;
    border-bottom: none;
    z-index: 20;
  }
  .insights-reader .header-inner {
    max-width: 1100px;
    margin: 0 auto;
    padding: 0 20px;
  }
  .insights-reader .header-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
  }
  .insights-reader .header-title-block { display: none; }
  .insights-reader .header-top > .tab-list {
    flex: 1;
    min-width: 0;
  }
  .insights-reader .header-actions {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-shrink: 0;
  }
  .insights-reader .home-link {
    display: inline-block;
    padding: 7px 14px;
    background: none;
    color: var(--text);
    text-decoration: none;
    border: 1px solid var(--card-border);
    border-radius: 6px;
    font-weight: 600;
    font-size: 0.8rem;
  }
  .insights-reader .home-link:hover { background: var(--active-bg); border-color: var(--text); opacity: 1; }
  .insights-reader .home-link { display: none; }
  .insights-reader header,
  .insights-reader .view-tabs {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .insights-reader .header-actions > .theme-toggle,
  .insights-reader .header-actions > .lang-switch,
  .insights-reader .mobile-menu-extras {
    display: none;
  }
  .insights-reader .header-inner,
  .insights-reader .layout,
  .insights-reader .footer-inner {
    max-width: 952px;
  }
  .insights-reader .view-tabs {
    max-width: none;
  }
  .insights-reader .view-tabs > .tab-list {
    max-width: 912px;
    margin-inline: auto;
  }
  .insights-reader .site-footer {
    display: none;
  }
  .insights-reader h1 {
    margin: 6px 0 4px;
    font-size: 1.25rem;
    font-weight: 600;
  }
  .insights-reader .subtitle {
    display: none;
    color: var(--muted);
    font-size: 0.85rem;
    margin-top: 8px;
    margin-bottom: 14px;
  }
  .insights-reader .lang-switch {
    display: flex;
    flex-shrink: 0;
    border: 1px solid var(--card-border);
    border-radius: 6px;
    overflow: hidden;
  }
  .insights-reader .theme-toggle {
    width: 34px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    padding: 0;
    border: 1px solid var(--card-border);
    border-radius: 6px;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    font: inherit;
    font-size: 0.9rem;
    line-height: 1;
    transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
  }
  .insights-reader .theme-toggle:hover {
    background: var(--lang-hover-bg);
    border-color: var(--text);
    color: var(--text);
  }
  .insights-reader .theme-toggle:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
  .insights-reader .lang-btn {
    padding: 6px 14px;
    font-size: 0.8rem;
    font-weight: 600;
    background: transparent;
    color: var(--muted);
    border: none;
    cursor: pointer;
    transition: background 0.25s ease, color 0.25s ease, opacity 0.25s ease;
  }
  .insights-reader .lang-btn.active { background: var(--lang-active-bg); color: var(--text); }
  .insights-reader .lang-btn:not(.active):hover { background: var(--lang-hover-bg); color: var(--text); }
  /* Match the Home interaction: hovering the other language previews the
     switch by returning the current language to its inactive appearance. */
  .insights-reader .lang-switch:has(.lang-btn:not(.active):hover) .lang-btn.active {
    background: transparent;
    color: var(--muted);
  }
  .insights-reader .search-wrap {
    position: relative;
    width: 100%;
    max-width: 600px;
  }
  .insights-reader #search {
    width: 100%;
    padding: 9px 32px 9px 14px;
    border-radius: 6px;
    border: 1px solid var(--card-border);
    background: var(--bg);
    color: var(--text);
    font-size: 0.95rem;
  }
  .insights-reader #search::placeholder { color: var(--muted); }
  .insights-reader #search:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
  .insights-reader .search-clear {
    display: none;
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    border: none;
    background: none;
    color: var(--muted);
    font-size: 1.2rem;
    line-height: 1;
    padding: 4px 6px;
    cursor: pointer;
    border-radius: 4px;
  }
  .insights-reader .search-clear:hover { color: var(--text); }
  .insights-reader .search-clear.visible { display: block; }
  /* Nested inside .tab-list, right after the Berichten tab button, so the
     filter is visibly scoped to that tab instead of floating as an
     unrelated global control. Only shown while Berichten is the active tab. */
  .insights-reader .category-filter {
    display: none;
    flex-basis: 100%;
    flex-wrap: wrap;
    gap: 6px 16px;
    padding: 8px 4px 10px;
    margin-bottom: 4px;
  }
  .insights-reader #tabMsgs.active ~ .category-filter { display: flex; }
  /* On the desktop flat tab row, the filter must render after all tab
     buttons (not between Berichten and Video's) so its flex-basis:100% wraps
     the row cleanly instead of splitting the tabs themselves across lines. */
  @media (min-width: 861px) {
    .insights-reader .category-filter { order: 99; }
    .insights-reader .view-tabs { display: none; }
  }
  .insights-reader .category-filter label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.82rem;
    color: var(--muted);
    cursor: pointer;
  }
  .insights-reader .category-filter input[type="checkbox"] {
    accent-color: var(--text);
    cursor: pointer;
  }
  .insights-reader .category-filter label:has(input:checked) { color: var(--text); }
  .insights-reader #count { color: var(--muted); font-size: 0.8rem; margin-top: 12px; }
  .insights-reader .site-footer {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 30;
    background: var(--header-bg);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-top: 1px solid var(--card-border);
    padding: 10px 0;
  }
  .insights-reader .footer-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    max-width: 1100px;
    margin: 0 auto;
    /* Matches .layout's own max-width + padding exactly, so the footer's
       left edge lines up with the content column above it instead of
       drifting 20px left of it (.site-footer used to carry this padding
       itself, which double-counts against .layout's own inset). */
    padding: 0 20px;
  }
  .insights-reader .footer-site-link {
    flex-shrink: 0;
    color: var(--muted);
    text-decoration: none;
    font-size: 0.8rem;
    font-weight: 600;
    text-shadow: 0 0 0 transparent;
    transition: color 0.35s ease, text-shadow 0.35s ease;
  }
  .insights-reader .footer-site-link:hover, .insights-reader .footer-site-link.emphasize {
    color: #f90706;
    text-shadow: 0 0 6px rgba(249,7,6,0.85), 0 0 16px rgba(249,7,6,0.55), 0 0 28px rgba(249,7,6,0.3);
  }
  .insights-reader .newsletter-form {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    flex: 1;
    min-width: 0;
  }
  .insights-reader .newsletter-form input[type="email"] {
    flex: 1;
    min-width: 140px;
    max-width: 320px;
    padding: 7px 12px;
    border-radius: 6px;
    border: 1px solid var(--card-border);
    background: var(--bg);
    color: var(--text);
    font-size: 0.85rem;
  }
  .insights-reader .newsletter-form input[type="email"]:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
  .insights-reader .newsletter-form input[type="email"]:disabled { opacity: 0.6; }
  .insights-reader .newsletter-form button {
    flex-shrink: 0;
    padding: 7px 14px;
    border-radius: 6px;
    border: 1px solid var(--card-border);
    background: none;
    color: var(--text);
    font-weight: 600;
    font-size: 0.8rem;
    cursor: pointer;
    font-family: inherit;
  }
  .insights-reader .newsletter-form button:hover { border-color: var(--text); opacity: 0.9; }
  .insights-reader .newsletter-form button:disabled { opacity: 0.7; cursor: default; }

  @media (max-width: 640px) {
    .insights-reader .site-footer { padding: 8px 0; }
    .insights-reader .footer-inner { padding: 0 14px; }
    .insights-reader .footer-site-link { display: none; }
    .insights-reader .newsletter-form { flex-wrap: nowrap; gap: 6px; }
    .insights-reader .newsletter-form input[type="email"] { min-width: 0; flex: 1 1 auto; padding: 6px 8px; font-size: 0.8rem; }
    .insights-reader .newsletter-form button { padding: 6px 10px; font-size: 0.72rem; }
  }

  .insights-reader .view-tabs {
    position: sticky;
    top: var(--header-h);
    /* Higher than header's z-index (20): .tab-list is a child of .view-tabs,
       so on mobile, when it opens as a position:fixed panel meant to cover
       the header, it's still confined to .view-tabs's own stacking context
       -- its own higher z-index only wins comparisons within that context,
       not against header itself, unless the context's root also out-ranks
       header's. */
    z-index: 21;
    background: var(--header-bg);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    max-width: 1100px;
    margin: 0 auto;
    padding: 8px 20px 0;
  }
  .insights-reader .tab-list {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
  }
  .insights-reader .toc-tab {
    padding: 10px 12px;
    border: none;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--muted);
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    font-family: inherit;
  }
  .insights-reader .toc-tab:hover { color: var(--text); }
  .insights-reader .toc-tab.active { color: var(--text); border-bottom-color: var(--text); }
  @media (min-width: 861px) {
    .insights-reader .toc-tab { font-size: 0.92rem; padding: 10px 14px; }
  }
  #menuToggle {
    display: none;
    align-items: center;
    justify-content: center;
    padding: 8px;
    border: none;
    background: none;
    cursor: pointer;
    font-family: inherit;
  }
  #menuToggle .menu-toggle-icon {
    position: relative;
    display: block;
    width: 18px;
    height: 2px;
    background: var(--text);
    border-radius: 1px;
    transition: opacity 0.15s ease;
  }
  #menuToggle .menu-toggle-icon::before, #menuToggle .menu-toggle-icon::after {
    content: '';
    position: absolute;
    left: 0;
    width: 18px;
    height: 2px;
    background: var(--text);
    border-radius: 1px;
    transition: top 0.15s ease, transform 0.15s ease;
  }
  #menuToggle .menu-toggle-icon::before { top: -6px; }
  #menuToggle .menu-toggle-icon::after { top: 6px; }
  #menuToggle[aria-expanded="true"] { background: var(--card-border); border-radius: 6px; }
  /* background:transparent, not opacity:0 -- opacity would also hide the
     ::before/::after pseudo-elements below (they're the ones drawing the X),
     since opacity applies to the whole element's rendering, pseudo-content
     included. */
  #menuToggle[aria-expanded="true"] .menu-toggle-icon { background: transparent; }
  #menuToggle[aria-expanded="true"] .menu-toggle-icon::before { top: 0; transform: rotate(45deg); }
  #menuToggle[aria-expanded="true"] .menu-toggle-icon::after { top: 0; transform: rotate(-45deg); }
  .insights-reader .mobile-menu-extras { display: none; }
  .insights-reader .menu-scrim { display: none; }
  .insights-reader .mobile-menu-site-link {
    display: none;
    /* .tab-list itself already contributes 8px of bottom padding below this,
       so less top padding than bottom keeps the text centered in its gap
       instead of sitting hard against the panel's bottom edge. */
    padding: 14px 0 6px;
    color: var(--muted);
    text-decoration: none;
    font-size: 0.82rem;
    font-weight: 600;
    text-shadow: 0 0 0 transparent;
    transition: color 0.35s ease, text-shadow 0.35s ease;
  }
  .insights-reader .mobile-menu-site-link:hover, .insights-reader .mobile-menu-site-link.emphasize {
    color: #f90706;
    text-shadow: 0 0 6px rgba(249,7,6,0.85), 0 0 16px rgba(249,7,6,0.55), 0 0 28px rgba(249,7,6,0.3);
  }

  @media (max-width: 860px) {
    .insights-reader .header-top {
      align-items: center;
      justify-content: flex-start;
      /* The real toggle lives in the fixed global header on mobile. Retain
         its former row height so page-4/search geometry does not jump. */
      min-height: 18px;
    }
    .insights-reader .header-actions .home-link,
    .insights-reader .header-actions .theme-toggle,
    .insights-reader .header-actions .lang-switch { display: none; }
    /* Keep the hamburger out of the moving reader header. It only takes
       over this fixed position once page 4 nearly fills the viewport. */
    #menuToggle {
      display: flex;
      position: absolute;
      left: 20px;
      top: 50%;
      z-index: 1;
      opacity: 0;
      visibility: hidden;
      pointer-events: none;
      transform: translateY(-50%);
      /* Delay visibility:hidden until the opacity exit has completed.
         Without that delay the button disappeared in one frame even though
         opacity itself had a transition. */
      transition: opacity 0.25s ease, visibility 0s linear 0.25s;
    }
    #menuToggle.takeover-ready,
    #menuToggle[aria-expanded="true"] {
      opacity: 1;
      visibility: visible;
      pointer-events: auto;
      transition: opacity 0.2s ease, visibility 0s linear 0s;
    }
    .insights-reader .mobile-menu-site-link { display: block; }
    .insights-reader h1 { font-size: 0.98rem; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .insights-reader .mobile-menu-extras {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 10px;
      padding: 4px 4px 12px;
      margin-bottom: 8px;
      border-bottom: 1px solid var(--card-border);
    }
    .insights-reader .view-tabs {
      display: flex;
      align-items: center;
      gap: 14px;
      padding: 12px 20px;
      border-bottom: 1px solid var(--card-border);
      /* A backdrop filter establishes a containing block for fixed
         descendants. That made the menu's top offset start below this
         already-offset search bar and effectively apply twice. Keep the
         mobile menu viewport-anchored so it can overlay the search field. */
      backdrop-filter: none;
      -webkit-backdrop-filter: none;
    }
    .insights-reader .view-tabs:has(#tabMsgs.active) { border-bottom: none; }
    .insights-reader .search-wrap { flex: 1; max-width: none; margin: 0; }
    .insights-reader .tab-list {
      display: none;
      position: fixed;
      left: 0;
      right: 0;
      top: var(--mobile-menu-top);
      z-index: 35;
      flex-direction: column;
      flex-wrap: nowrap;
      gap: 0;
      background: var(--bg);
      border-bottom: 1px solid var(--card-border);
      border-radius: 0 0 16px 16px;
      box-shadow: 0 8px 16px rgba(0,0,0,0.2);
      padding: 4px 20px 8px;
      max-height: calc(100vh - var(--mobile-menu-top) - var(--footer-h));
      overflow-y: auto;
    }
    .insights-reader .tab-list.open { display: flex; }
    /* Continue the menu panel's solid background through the global control
       row. The row stays above the panel and its language/theme controls
       remain usable, but the two areas now read as one continuous surface. */
    body:has(.insights-reader .tab-list.open) > header {
      background: var(--bg);
    }
    /* Always present (not display:none) once on mobile, so opacity/blur can
       actually transition -- display can't be animated, and toggling it
       would make the fade instant. Invisible and non-interactive at rest. */
    .insights-reader .menu-scrim {
      display: block;
      position: fixed;
      top: var(--mobile-menu-top);
      left: 0;
      right: 0;
      bottom: 0;
      z-index: 34;
      opacity: 0;
      pointer-events: none;
      backdrop-filter: blur(0px);
      -webkit-backdrop-filter: blur(0px);
      transition: opacity 0.2s ease, backdrop-filter 0.2s ease, -webkit-backdrop-filter 0.2s ease;
    }
    .insights-reader .menu-scrim.open {
      opacity: 1;
      pointer-events: auto;
      backdrop-filter: blur(6px);
      -webkit-backdrop-filter: blur(6px);
    }
    /* Keep intercepting taps while the menu items leave, while already
       reversing the blur. Removing .open only after the item tween would
       postpone this transition and make the background snap back into focus. */
    .insights-reader .menu-scrim.open.closing {
      opacity: 0;
      pointer-events: auto;
      backdrop-filter: blur(0px);
      -webkit-backdrop-filter: blur(0px);
      transition-duration: 0.35s;
    }
    .insights-reader .toc-tab {
      width: 100%;
      text-align: left;
      padding: 12px 4px;
      border-bottom: 1px solid var(--card-border);
    }
    .insights-reader .toc-tab:last-child { border-bottom: none; }
    /* The checkbox filters belong visually to the Berichten tab, so the
       separator line moves from right under "Berichten" to right under the
       checkboxes -- grouping them inside the same bordered section instead
       of the border cutting between the tab and its own filter controls. */
    .insights-reader #tabMsgs.active { border-bottom: none; }
    .insights-reader .category-filter {
      flex-basis: auto;
      padding: 2px 4px 10px;
      border-bottom: 1px solid var(--card-border);
    }
  }

  @media (max-width: 640px) {
    .insights-reader {
      /* The global header uses 14px vertical padding at phone widths:
         14px + 30px control height + 8px breathing room. */
      --mobile-menu-top: 52px;
    }
  }

  .insights-reader .layout {
    max-width: 1100px;
    margin: 0 auto;
    padding: 12px 20px 20px;
    display: grid;
    grid-template-columns: 280px minmax(0, 760px);
    gap: 24px;
    align-items: start;
  }
  .insights-reader .toc-wrap {
    position: sticky;
    /* --tabs-h already includes the category filter's height since it now
       lives inside .view-tabs, so it isn't added again here. The extra 12px
       matches .layout's own top padding -- without it, the gap above the
       sidebar is 12px before it starts sticking and 0px once it does. */
    top: calc(var(--header-h) + var(--tabs-h) + 12px);
    /* Must also subtract --footer-h: the fixed footer bar sits on top of
       whatever's beneath it, so without this the panel's true bottom edge
       (and the page's own document height) both ran ~50px past the actual
       visible area above the footer -- clipping the last bit of content
       behind it and leaving a spurious, barely-functional page scrollbar. */
    max-height: calc(100vh - var(--header-h) - var(--tabs-h) - var(--footer-h) - 32px);
    display: flex;
    flex-direction: column;
    /* The rounded frame (background/border/radius) lives here, one level up
       from the scrolling + edge-fade-masked nav#toc, so the mask only ever
       fades the *content* toward this solid background -- the frame's own
       corners stay crisp and never get faded/clipped by the mask. */
    background:
      linear-gradient(180deg, var(--card-highlight), transparent 90px),
      var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    overflow: hidden;
  }
  .insights-reader nav#toc {
    overflow-y: auto;
    padding: 10px 6px;
  }
  /* Thin, theme-colored scrollbars instead of the bulky OS default, on the
     two independently-scrolling panes (nav#toc, .msg-scroll). Firefox reads
     scrollbar-width/-color; everything WebKit-based reads the pseudo-
     elements below -- both are needed to cover browsers. */
  .insights-reader nav#toc, .insights-reader .msg-scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--card-border) transparent;
  }
  .insights-reader nav#toc::-webkit-scrollbar, .insights-reader .msg-scroll::-webkit-scrollbar { width: 8px; }
  .insights-reader nav#toc::-webkit-scrollbar-track, .insights-reader .msg-scroll::-webkit-scrollbar-track { background: transparent; }
  .insights-reader nav#toc::-webkit-scrollbar-thumb, .insights-reader .msg-scroll::-webkit-scrollbar-thumb {
    background: var(--card-border);
    border-radius: 4px;
  }
  .insights-reader nav#toc::-webkit-scrollbar-thumb:hover, .insights-reader .msg-scroll::-webkit-scrollbar-thumb:hover { background: var(--muted); }
  /* Invites scrolling the TOC -- only shown while there's actually more
     below (toggled in updateEdgeFade()), gently bobbing to draw the eye. */
  .insights-reader .scroll-hint {
    position: absolute;
    bottom: 4px;
    left: 50%;
    transform: translateX(-50%);
    color: var(--muted);
    font-size: 0.7rem;
    line-height: 1;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.25s ease;
    animation: scroll-hint-bounce 1.6s ease-in-out infinite;
  }
  .insights-reader .scroll-hint.visible { opacity: 0.65; }
  @keyframes scroll-hint-bounce {
    0%, 100% { transform: translateX(-50%) translateY(0); }
    50% { transform: translateX(-50%) translateY(3px); }
  }
  .insights-reader .mobile-toc-bar { display: none; }
  .insights-reader #tocSelect {
    width: 100%;
    padding: 9px 12px;
    border-radius: 6px;
    border: 1px solid var(--card-border);
    background: var(--bg);
    color: var(--text);
    font-size: 0.85rem;
    font-family: inherit;
  }
  .insights-reader #tocSelect:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
  .insights-reader .toc-month h3 {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    margin: 14px 10px 4px;
  }
  .insights-reader .toc-month:first-child h3 { margin-top: 6px; }
  .insights-reader .toc-link {
    display: flex;
    gap: 8px;
    align-items: baseline;
    padding: 6px 8px;
    border-left: 2px solid transparent;
    border-radius: 6px;
    text-decoration: none;
    color: var(--text);
    font-size: 0.82rem;
    line-height: 1.3;
  }
  .insights-reader .toc-link:hover { background: var(--bg); }
  .insights-reader .toc-link.active {
    background: var(--active-bg);
    border-left-color: var(--text);
    color: var(--text);
    font-weight: 600;
  }
  .insights-reader .toc-day { color: var(--muted); flex-shrink: 0; font-variant-numeric: tabular-nums; font-size: 0.75rem; }
  .insights-reader .toc-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .insights-reader .toc-link.hidden { display: none; }

  .insights-reader #videoView {
    max-width: 952px;
    margin: 0 auto;
    padding: 20px;
    display: none;
  }
  .insights-reader .video-month { margin-bottom: 22px; }
  .insights-reader .video-month h3 {
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    margin: 0 0 10px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--card-border);
  }
  .insights-reader .video-item {
    display: flex;
    align-items: baseline;
    gap: 16px;
    padding: 12px 4px;
    border-bottom: 1px solid var(--card-border);
    flex-wrap: wrap;
  }
  .insights-reader .video-item:last-child { border-bottom: none; }
  .insights-reader .video-date {
    flex-shrink: 0;
    width: 110px;
    color: var(--muted);
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .insights-reader .video-title {
    flex: 1;
    min-width: 240px;
    color: var(--text);
    text-decoration: none;
    font-size: 0.95rem;
    line-height: 1.4;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .insights-reader .video-title:hover { color: var(--accent); text-decoration: underline; }
  .insights-reader .video-duration {
    flex-shrink: 0;
    width: 56px;
    text-align: right;
    color: var(--muted);
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }
  @media (max-width: 640px) {
    .insights-reader .video-item { gap: 8px; }
    .insights-reader .video-date { width: auto; order: 1; margin-bottom: 2px; }
    .insights-reader .video-title { order: 2; flex-basis: 100%; width: 100%; min-width: 0; }
    .insights-reader .video-duration { order: 3; width: auto; text-align: left; }
    .insights-reader #videoView .video-duration { display: none; }
  }

  .insights-reader #tagsView {
    max-width: 952px;
    margin: 0 auto;
    padding: 20px;
    display: none;
  }
  .insights-reader .tags-intro { color: var(--muted); font-size: 0.85rem; margin: 0 0 20px; }
  .insights-reader .tag-cloud {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
  }
  .insights-reader .tag-pill {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    color: var(--text);
    border-radius: 999px;
    padding: 6px 14px;
    cursor: pointer;
    line-height: 1.3;
    font-family: inherit;
  }
  .insights-reader .tag-pill:hover { border-color: var(--text); color: var(--text); }
  .insights-reader .tag-count { color: var(--muted); font-size: 0.75em; }
  .insights-reader .tag-pill:hover .tag-count { color: var(--text); }

  .insights-reader #glossaryView {
    max-width: 952px;
    margin: 0 auto;
    padding: 20px;
    display: none;
  }
  .insights-reader .glossary-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
  }
  .insights-reader .glossary-item {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    padding: 14px 18px;
  }
  .insights-reader .glossary-term {
    display: block;
    background: none;
    border: none;
    padding: 0 0 6px;
    margin: 0;
    color: var(--text);
    font-weight: 700;
    font-size: 0.95rem;
    cursor: pointer;
    font-family: inherit;
    text-align: left;
  }
  .insights-reader .glossary-term:hover { text-decoration: underline; }
  .insights-reader .glossary-def {
    margin: 0;
    color: var(--text);
    font-size: 0.85rem;
    line-height: 1.5;
  }

  .insights-reader #quotesView {
    max-width: 952px;
    margin: 0 auto;
    padding: 20px;
    display: none;
  }
  .insights-reader .quote-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 16px;
  }
  .insights-reader .quote-card {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    padding: 16px 18px;
    margin: 0;
  }
  .insights-reader .quote-text {
    margin: 0 0 10px;
    font-size: 1rem;
    line-height: 1.5;
    font-style: italic;
    color: var(--text);
  }
  .insights-reader .quote-meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    font-size: 0.82rem;
  }
  .insights-reader .quote-author { color: var(--muted); font-weight: 600; font-style: normal; }
  .insights-reader .quote-context { color: var(--muted); text-decoration: none; white-space: nowrap; }
  .insights-reader .quote-context:hover { color: var(--accent); }

  .insights-reader #favoritesView {
    max-width: 952px;
    margin: 0 auto;
    padding: 20px;
    display: none;
  }
  .insights-reader .favorites-intro {
    color: var(--muted);
    font-size: 0.85rem;
    margin: 0 0 18px;
  }
  .insights-reader .favorites-list {
    display: grid;
    gap: 10px;
  }
  .insights-reader .favorite-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 12px;
    align-items: center;
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    padding: 14px 16px;
  }
  .insights-reader .favorite-item-link {
    min-width: 0;
    color: var(--text);
    text-decoration: none;
  }
  .insights-reader .favorite-item-link:hover .favorite-item-title { text-decoration: underline; }
  .insights-reader .favorite-item-title {
    display: block;
    font-size: 0.94rem;
    font-weight: 650;
    line-height: 1.4;
  }
  .insights-reader .favorite-item-date {
    display: block;
    color: var(--muted);
    font-size: 0.78rem;
    margin-top: 4px;
  }
  .insights-reader .favorites-empty {
    color: var(--muted);
    border: 1px dashed var(--card-border);
    border-radius: 8px;
    padding: 28px 20px;
    text-align: center;
  }
  .insights-reader .favorite-btn {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    display: inline-grid;
    place-items: center;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: var(--muted);
    font: inherit;
    font-size: 1.15rem;
    line-height: 1;
    cursor: pointer;
    transition: color 160ms ease, border-color 160ms ease, text-shadow 160ms ease, transform 160ms ease;
  }
  .insights-reader .favorite-btn:not(.active) {
    color: var(--muted);
    text-shadow: none;
  }
  .insights-reader .favorite-btn:hover {
    color: #ff5a52;
    text-shadow: 0 0 11px rgba(255, 74, 66, 0.68);
    transform: scale(1.06);
  }
  .insights-reader .favorite-btn:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
  }
  .insights-reader .favorite-btn.active {
    color: #c0392b;
    text-shadow: 0 0 8px rgba(192, 57, 43, 0.42);
  }
  .insights-reader .favorite-btn.active:hover {
    color: #ff5a52;
    text-shadow: 0 0 12px rgba(255, 74, 66, 0.72);
  }
  .insights-reader .favorite-item .favorite-btn { align-self: center; }

  .insights-reader #externalView {
    max-width: 952px;
    margin: 0 auto;
    padding: 20px;
    display: none;
  }

  .insights-reader #adminView {
    max-width: 1100px;
    margin: 0 auto;
    padding: 20px;
    display: none;
  }
  .insights-reader .admin-intro { color: var(--muted); font-size: 0.85rem; margin: 0 0 24px; max-width: 70ch; }
  .insights-reader .admin-heading { font-size: 1rem; margin: 28px 0 10px; }
  .insights-reader .admin-heading:first-of-type { margin-top: 0; }
  .insights-reader .admin-subheading {
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    margin: 18px 0 6px;
  }
  .insights-reader .admin-subheading:first-child { margin-top: 0; }
  .insights-reader .admin-section {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    padding: 4px 16px;
  }
  .insights-reader .admin-empty { color: var(--muted); font-size: 0.85rem; margin: 10px 0; }
  .insights-reader .admin-static-label { color: var(--text); }
  .insights-reader .admin-id { color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.72rem; }
  .insights-reader .admin-export-btn {
    margin-top: 20px;
    padding: 9px 18px;
    border-radius: 6px;
    border: 1px solid var(--text);
    background: var(--text);
    color: var(--bg);
    font-size: 0.88rem;
    font-weight: 600;
    cursor: pointer;
  }
  .insights-reader .admin-export-btn:hover { opacity: 0.9; }
  .insights-reader .admin-unhide-btn {
    flex-shrink: 0;
    border: 1px solid var(--card-border);
    background: transparent;
    color: var(--muted);
    border-radius: 5px;
    padding: 3px 10px;
    font-size: 0.75rem;
    cursor: pointer;
  }
  .insights-reader .admin-unhide-btn:hover { border-color: var(--text); color: var(--text); }

  .insights-reader .hide-btn {
    display: none;
    flex-shrink: 0;
    border: 1px solid var(--card-border);
    background: transparent;
    color: var(--muted);
    border-radius: 5px;
    padding: 3px 10px;
    font-size: 0.75rem;
    cursor: pointer;
    margin-left: auto;
  }
  @media (min-width: 861px) {
    .insights-reader .local-dev .hide-btn { display: inline-block; }
  }
  .insights-reader .hide-btn:hover { border-color: var(--trim, #c0392b); color: #c0392b; }
  .insights-reader .msg.session-hidden, .insights-reader .video-item.session-hidden { display: none; }

  .insights-reader .internal-ref { color: var(--accent); text-decoration: none; }
  .insights-reader .internal-ref:hover { text-decoration: underline; }

  .insights-reader main {
    /* Mirrors .toc-wrap's sticky offset + max-height exactly, so main's own
       top edge never moves as its content scrolls. Positioning and
       scrolling are deliberately split across two elements (same pattern as
       .toc-wrap + nav#toc): a position:sticky element that is *also* its
       own overflow-y:auto scroll container confuses scrollIntoView()'s
       nearest-scrollable-ancestor walk in Chrome -- it silently no-ops
       instead of scrolling. .msg-scroll below carries the actual scrolling.
       Unlike .toc-wrap, main has no border/radius/background of its own --
       each .msg card already draws its own rounded frame, so wrapping the
       whole column in a second one nested around those just looked doubled
       up. */
    position: sticky;
    top: calc(var(--header-h) + var(--tabs-h) + 12px);
    /* Must also subtract --footer-h -- see the identical note on .toc-wrap. */
    max-height: calc(100vh - var(--header-h) - var(--tabs-h) - var(--footer-h) - 32px);
    display: flex;
    min-width: 0;
  }
  .insights-reader .msg-scroll {
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
    flex: 1;
    /* proximity, not mandatory -- settles on a full card (so its rounded
       corners are never left half-clipped by the scroll boundary) without
       fighting free scrolling through a message taller than the viewport. */
    scroll-snap-type: y proximity;
  }
  .insights-reader .msg {
    scroll-snap-align: start;
    background:
      linear-gradient(180deg, var(--card-highlight), transparent 90px),
      var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    padding: 14px 18px;
    scroll-margin-top: calc(var(--header-h) + var(--tabs-h) + var(--toc-bar-h) + 12px);
  }
  @media (min-width: 861px) {
    /* main is now its own scroll container (see main{} above), already
       positioned below the sticky header/tabs -- jumping to a message only
       needs a small buffer from main's own top edge, not the full header
       + tabs stack again. */
    .insights-reader .msg { scroll-margin-top: 12px; }
  }
  .insights-reader .msg.flash { animation: flash 1.4s ease-out; }
  @keyframes flash {
    0% { box-shadow: 0 0 0 2px var(--accent); }
    100% { box-shadow: 0 0 0 0 transparent; }
  }
  .insights-reader .msg-meta {
    display: flex;
    gap: 10px;
    align-items: baseline;
    margin-bottom: 8px;
    font-size: 0.78rem;
    color: var(--muted);
  }
  .insights-reader .msg-date { font-weight: 600; color: var(--text); }
  .insights-reader .msg-text { line-height: 1.55; font-size: 0.95rem; white-space: normal; }
  .insights-reader .msg-text a { color: var(--accent); word-break: break-all; }
  .insights-reader .msg-text a, .insights-reader .internal-ref {
    text-decoration-color: color-mix(in srgb, var(--text) 45%, transparent);
    text-underline-offset: 2px;
  }
  .insights-reader .msg-text p { margin: 0 0 1em; }
  .insights-reader .msg-text p:last-child { margin-bottom: 0; }
  .insights-reader .msg-list { margin: 0 0 1em; padding-left: 1.4em; }
  .insights-reader .msg-list:last-child { margin-bottom: 0; }
  .insights-reader .msg-list li { margin-bottom: 0.4em; }
  .insights-reader .msg-list li:last-child { margin-bottom: 0; }
  .insights-reader ul.msg-list { list-style-type: disc; }
  .insights-reader ol.msg-list { list-style-type: decimal; }
  .insights-reader ol.msg-list li::marker { color: var(--accent); font-weight: 600; }
  .insights-reader ul.msg-list li::marker { color: var(--accent); }
  .insights-reader .chart-thumb {
    display: inline-block;
    margin: 6px 0;
  }
  .insights-reader .chart-thumb img {
    display: block;
    max-width: 100%;
    width: 480px;
    height: auto;
    border-radius: 8px;
    border: 1px solid var(--card-border);
  }
  .insights-reader .chart-thumb:hover img { border-color: var(--accent); }
  .insights-reader .msg-images {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 10px 0;
  }
  .insights-reader .msg-image { display: inline-block; }
  .insights-reader .msg-image img {
    display: block;
    max-width: 100%;
    max-height: 420px;
    width: auto;
    height: auto;
    border-radius: 8px;
    border: 1px solid var(--card-border);
  }
  .insights-reader .msg-image:hover img { border-color: var(--accent); }
  .insights-reader .embeds { margin-top: 10px; }
  .insights-reader .embed {
    background: var(--bg);
    border-left: 3px solid var(--accent);
    padding: 8px 12px;
    border-radius: 4px;
    font-size: 0.82rem;
    color: var(--muted);
    margin-top: 6px;
    white-space: pre-line;
  }
  .insights-reader .msg.hidden { display: none; }
  .insights-reader .video-item.hidden, .insights-reader .video-month.hidden, .insights-reader .quote-card.hidden, .insights-reader .tag-pill.hidden, .insights-reader .glossary-item.hidden { display: none; }
  .insights-reader mark { background: #ffe066; color: #1a1a1a; border-radius: 2px; padding: 0 1px; }
  :root[data-theme="dark"] .insights-reader mark { background: #a67c00; color: #fff; }

  @media (max-width: 860px) {
    .insights-reader .subtitle, .insights-reader #count { display: none; }
    .insights-reader .layout { grid-template-columns: minmax(0, 1fr); }
    .insights-reader .toc-wrap { display: none; }
    /* main stays sticky + bounded on mobile too (same framed, internally-
       scrolling pane as desktop) -- it just also has to sit below the
       mobile TOC dropdown bar, which desktop doesn't have. */
    .insights-reader main {
      top: calc(var(--header-h) + var(--tabs-h) + var(--toc-bar-h) + 12px);
      max-height: calc(100vh - var(--header-h) - var(--tabs-h) - var(--toc-bar-h) - var(--footer-h) - 32px);
    }
    .insights-reader .mobile-toc-bar.visible {
      display: block;
      position: sticky;
      top: calc(var(--header-h) + var(--tabs-h));
      z-index: 14;
      background: var(--bg);
      max-width: 1100px;
      margin: 0 auto;
      padding: 0 20px 10px;
      border-bottom: 1px solid var(--card-border);
    }
  }

  /* Final integration overrides intentionally come after the reader CSS. */
  .insights-reader {
    box-sizing: border-box;
    height: 100%;
    padding-bottom: 0;
  }
  .insights-reader header {
    display: block;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  /* The global light-theme header has a more specific 1px divider rule.
     Page 4 intentionally has no header divider in either theme. */
  :root[data-theme="light"] .insights-reader header {
    border-bottom: none;
  }
  .insights-reader .header-actions > .theme-toggle,
  .insights-reader .header-actions > .lang-switch,
  .insights-reader .mobile-menu-extras,
  .insights-reader .home-link,
  .insights-reader .site-footer {
    display: none;
  }
  .insights-reader .header-inner,
  .insights-reader .layout,
  .insights-reader .footer-inner {
    max-width: 952px;
  }
  .insights-reader .view-tabs {
    max-width: none;
  }
  .insights-reader .view-tabs > .tab-list {
    max-width: 912px;
    margin-inline: auto;
  }
