/* Block: Video
 *
 * Two rendering paths share the same outer frame (.block-video-player):
 *
 *   has-facade   — YouTube: <img class="block-video-thumb"> poster + orange
 *                  <button class="block-video-play"> play button. Click
 *                  swaps the inner DOM for the YouTube iframe and JS flips
 *                  .is-playing on the player.
 *   --live       — Self-hosted: the EJECTED Video.js v10 default skin (per
 *                  https://videojs.org/docs/framework/html/how-to/customize-skins)
 *                  <video-player><media-container class="media-default-skin
 *                  media-default-skin--video block-video-skin">. Baseline
 *                  default-skin CSS lives in
 *                  css/components/videojs/video-skin.css (verbatim
 *                  copy from the package). Everything in this file is OUR
 *                  overrides on top of that.
 *
 * All pre/post-play UI for self-hosted is driven by native attributes that
 * the Video.js components already expose — no JS class flipping needed:
 *   - <media-poster>       has data-visible until the first play/seek.
 *   - <media-play-button>  carries data-paused/data-started/data-ended
 *                          reflecting the live media state. Our central
 *                          orange overlay IS a <media-play-button>, so it
 *                          naturally appears on pause (pre-play AND
 *                          post-play) and disappears on play.
 *   - <video>:paused       CSS pseudo-class used to gate the control bar
 *                          visibility on pointer:fine so a paused player
 *                          hides its controls when the mouse leaves the
 *                          frame.
 *
 * The YouTube facade keeps its .is-playing flag (JS-driven) because that
 * path doesn't mount Video.js components.
 */

.block-video {
    display: flex;
    justify-content: center;
}

@container page-content (max-width: 768px) {
    .block-video {
        padding: 0 10px;
    }
}

.block-video-player {
    position: relative;
    width: 100%;
    max-width: calc(90vh * 16 / 9);
    aspect-ratio: 16 / 9;
    border-radius: var(--border-radius-lg);
    overflow: hidden;
    background: #000;
}

.block-video-player.has-facade {
    cursor: pointer;
}

/* YouTube facade: thumbnail image and iframe fill the 16:9 frame.
   For self-hosted, <video-player> is display:contents (from the plugin's
   base stylesheet) — the visual box is the <media-container>, which the
   default skin CSS already sizes to 100% width/height inside its parent. */
.block-video-thumb,
.block-video-player iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

.block-video-thumb {
    object-fit: cover;
    display: block;
}

/* ---- Self-hosted (.block-video-skin class on <media-container>) ---- */

/* Customize the ejected skin through the CSS vars the baseline uses.
   The outer border-radius is owned by .block-video-player; we want video
   to cover instead of contain (plugin default). */
.block-video-skin {
    --media-border-radius: 0;
    --media-video-border-radius: 0;
    --media-object-fit: cover;
    font-family: 'Founders Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

/* Pre-play: while <media-poster> is visible, hide the control bar AND the
   gradient scrim without using display:none, so when the poster goes away
   the skin's OWN transition-property (scale, filter, opacity) runs the
   reveal animation. We just mirror the hidden state it already defines
   for `.media-controls:not([data-visible])`. */
.block-video-skin:has(media-poster[data-visible]) .media-controls {
    opacity: 0;
    scale: 0.9;
    filter: blur(8px);
    pointer-events: none;
}
.block-video-skin:has(media-poster[data-visible]) .media-overlay {
    opacity: 0;
    pointer-events: none;
}

/* Match the first-reveal duration to the hide duration. The default skin
   ships an asymmetric transition: `--media-controls-transition-duration`
   is 100ms by default and only switches to 300ms while the root matches
   `:has(.media-controls:not([data-visible]))` — so native idle-hide is
   300ms but native reveal is 100ms (feels snappy / "dry" coming off the
   poster). Pinning the duration on our `.media-controls` to the same
   per-pointer values the native `:has(not[data-visible])` rule uses
   keeps BOTH directions in sync, so the scale+blur fade-in looks
   identical to the fade-out. Values mirror skin.css lines 968-979 so
   reduced-motion and touch defaults still apply. */
.block-video-skin .media-controls {
    @media (pointer: fine) {
        transition-duration: 300ms;
    }
    @media (pointer: coarse) {
        transition-duration: 150ms;
    }
    @media (prefers-reduced-motion: reduce) {
        transition-duration: 50ms;
    }
}

/* Kill the control bar's wrap-to-2-rows at narrow widths — since we strip
   the seek / pip / playback-rate buttons the remaining set (play, time,
   slider, volume, captions, cast, fullscreen) fits comfortably in one row
   at any reasonable player width. */
.block-video-skin .media-controls {
    flex-wrap: nowrap;
    column-gap: 0.125rem;
}
.block-video-skin .media-controls .media-time-controls {
    order: unset;
    flex: 1;
}
.block-video-skin .media-controls .media-button-group:first-child,
.block-video-skin .media-controls .media-button-group:last-child {
    flex: 0 0 auto;
}

/* Orange round play overlay. It IS a <media-play-button>, so it natively
   toggles play/pause without JS and reflects the current media state via
   attributes. We key visibility off [data-paused] — present before the
   first play AND whenever the user pauses after playing — so the overlay
   behaves like a classic poster play button: appears on pause, disappears
   on play, with a scale+opacity spring. Scale 0 → 1 plays on reveal,
   mirrors on hide. Because we never use display:none the transition is
   symmetric and pointer-events toggle follows visibility so the button
   doesn't eat clicks aimed at the controls while invisible. */
.block-video-play-overlay {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: 5;
    width: clamp(56px, 8cqi, 96px);
    aspect-ratio: 1;
    translate: -50% -50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: 0;
    padding: 0;
    color: inherit;
    cursor: pointer;
    opacity: 0;
    scale: 0.7;
    pointer-events: none;
    transition: scale 300ms ease-out, opacity 300ms ease-out;
}

.block-video-skin .block-video-play-overlay[data-paused] {
    opacity: 1;
    scale: 1;
    pointer-events: auto;
}

.block-video-play-overlay svg {
    width: 100%;
    height: 100%;
    display: block;
    transition: transform 0.2s ease;
}

.block-video-play-overlay svg path {
    transition: fill 0.2s ease;
}

.block-video-play-overlay:hover svg {
    transform: scale(1.05);
}

.block-video-play-overlay:hover svg path {
    fill: var(--color-sand);
}

/* Paused = controls visible by design (Video.js Controls feature).
   See .cursor/rules/video-js.mdc -> Features -> Controls. */

/* Remove the black gradient scrim. The element stays in the DOM so the
   error-dialog backdrop-filter still works; we only zero the background. */
.block-video-skin .media-overlay {
    background: none;
}

/* Volume popover vertical slider — two tweaks over the ejected skin:

   1. Tighten the container (2rem default -> 1rem) so it looks proportional
      to the 0.75rem persistent thumb. The 0.25rem track stays centered;
      hit area is still generous enough for mouse.
   2. Force the thumb's vertical position via ancestor selectors. The skin
      ships this rule keyed off data-orientation on the thumb element
      itself — and in our ejected flow that child attribute occasionally
      doesn't land in time, leaving the thumb frozen at the flex-center
      default while volume (and the fill bar) change correctly. Keying
      off the popover context side-steps the timing entirely since the
      popover slider is ALWAYS vertical. */
.block-video-skin .media-popover--volume .media-slider[data-orientation="vertical"] {
    width: 1rem;
}
.block-video-skin .media-popover--volume .media-slider__thumb {
    left: 50%;
    top: calc(100% - var(--media-slider-fill));
}
.block-video-skin .media-popover--volume .media-slider[data-dragging] .media-slider__thumb {
    top: calc(100% - var(--media-slider-pointer));
}

/* Hidden audio siblings (voice / BGM) rendered by PHP and synced in JS. */
.block-video-player .block-video-audio {
    display: none;
}

/* ---- YouTube facade play button ---- */

.block-video-play {
    position: absolute;
    inset: 0;
    z-index: 3;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    color: inherit;
}

.block-video-play svg {
    width: clamp(40px, 5cqi, 72px);
    height: clamp(40px, 5cqi, 72px);
    transition: transform 0.2s ease;
}

.block-video-player:hover .block-video-play svg {
    transform: scale(1.1);
}

.block-video-player .block-video-play svg path { transition: fill 0.2s ease; }

.block-video-player:hover .block-video-play svg path {
    fill: var(--color-sand);
}

/* Facade: once YouTube mounts, thumb + button disappear. */
.block-video-player.is-playing .block-video-thumb,
.block-video-player.is-playing .block-video-play--facade {
    display: none;
}

/* ---- Bottom slot (generic) ----
   Content-agnostic shelf at the bottom of any .block-video-player. Slides
   away once playback starts. Both rendering paths flip .is-playing on the
   player at their "first play happened" moment (YouTube: on click, before
   iframe mount; self-hosted: on the first native 'play' event), so a
   single selector owns the animation. */

.block-video-bottom {
    position: absolute;
    left: 12px;
    right: 12px;
    bottom: 12px;
    z-index: 5;
    margin: 0;
    transition: transform 0.4s ease, opacity 0.4s ease;
    pointer-events: auto;
}

.block-video-player.is-playing .block-video-bottom,
.block-video-pip-card.is-expanding .block-video-bottom {
    transform: translateY(calc(100% + 20px));
    opacity: 0;
    pointer-events: none;
}

.block-video-bottom-cta {
    display: inline-block;
    margin: 0;
    max-width: 100%;
    color: var(--color-sand);
    font-size: 15px;
    line-height: 1.4;
    background: rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-radius: var(--border-radius-sm);
    padding: 8px 12px;
    text-wrap: balance;
}

/* ---- PIP variant ---- */

.block-video--pip {
    display: block;
}

.block-video-pip-wrap {
    position: relative;
    width: 100%;
    overflow: hidden;
    border-radius: var(--border-radius-lg);
}

/* The backdrop surface lives inside the wrap as a .bio-video-surface
   (see template-parts/pages/product/product-blocks/partials/video-image.php). Positioning
   of the <video> layer is handled by video-surface.css; these rules just
   give the image/video the right dimensions and the poster crossfade
   transition. The <img> drives the wrap's intrinsic height in both
   image-only and has-video modes (shared CSS promotes it to relative). */
.block-video-pip-image-wrap {
    width: 100%;
}

.block-video-pip-image {
    width: 100%;
    height: auto;
    max-height: 95dvh;
    object-fit: cover;
    display: block;
    transition: transform 0.5s ease;
}

/* .block-video-pip-image-wrap.has-video .block-video-pip-video {
    max-height: 90dvh;
} */

.block-video-pip-wrap:hover .block-video-pip-image {
    transform: none;
}

.block-video-pip-wrap::after {
    content: '';
    position: absolute;
    inset: 0;
    background: #000;
    opacity: var(--pip-overlay, 0);
    pointer-events: none;
    z-index: 1;
}

.block-video-pip-card {
    position: absolute;
    bottom: var(--spacing-gap-medium);
    left: var(--spacing-gap-medium);
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 35%;
    min-width: 400px;
    max-width: 600px;
    max-height: calc(100% - (var(--spacing-gap-medium) * 2));
    transition: width 0.5s ease, max-width 0.5s ease;
}

/* Expand on user engagement: YouTube facade adds .is-expanding on click
   (before the iframe mounts, so the card grows first then the video fades
   in); self-hosted flips .is-playing on first 'play'. */
.block-video-pip-card.is-expanding,
.block-video-pip-card:has(.block-video-player.is-playing) {
    width: 50%;
    max-width: 2000px;
}

.block-video--pip .block-video-player {
    position: relative;
    width: 100%;
    max-width: none;
    border-radius: var(--border-radius-sm);
    overflow: hidden;
}

.block-video--pip .block-video-thumb {
    transition: transform 0.5s ease;
}

.block-video--pip .block-video-player:hover .block-video-thumb {
    transform: scale(1.03);
}

.block-video--pip .block-video-play svg {
    width: clamp(28px, 3cqi, 48px);
    height: clamp(28px, 3cqi, 48px);
}

@container page-content (max-width: 1024px) {
    .block-video-pip-image-wrap,
    .block-video-pip-image { display: none; }
    .block-video--pip .block-video-bottom { display: none; }

    /* PIP loses the backdrop at this breakpoint, so the card collapses
       into the normal flow of the (now padding-respecting) .block wrapper.
       The block's own horizontal padding + the --border-radius-md cascade
       on both .block-video-pip-wrap and .block-video-player keep the video
       inside the wrapper with proper corners — matching the full-image
       scrub-pinned pattern so every video surface looks coherent. */
    .block-video-pip-card,
    .block-video-pip-card.is-expanding,
    .block-video-pip-card:has(.block-video-player.is-playing) {
        position: relative;
        bottom: unset;
        left: unset;
        width: 100%;
        min-width: unset;
        max-width: unset;
        max-height: unset;
        transition: none;
    }

    .block-video--pip .block-video-play svg {
        width: 20%;
        height: auto;
        max-width: 50px;
    }

    .block-video-player {
        border-radius: var(--border-radius-md);
        max-width: none;
    }

    .block-video-pip-wrap {
        border-radius: var(--border-radius-md);
    }
}

@container page-content (max-width: 768px) {
    .block-video-player {
        border-radius: var(--border-radius-sm);
        max-width: none;
    }

    .block-video-pip-wrap {
        border-radius: var(--border-radius-sm);
    }
}

/* Language pickers (captions + audio). Triggers reuse the skin's
   .media-button primitives for hover/focus states; only the popover body
   (our <ul>) is custom since Video.js v10 HTML doesn't ship a menu
   primitive. The popover itself still reuses .media-surface for
   backdrop-filter + border + shadow. Structure is shared between the
   two pickers via .block-video-picker-* classes; semantic variants
   (.block-video-captions-item / .block-video-audio-item) exist for JS
   targeting only. */
.block-video-skin .block-video-picker-popover {
    padding: 0.375rem;
    border-radius: 0.5rem;
    min-width: 11rem;
}
.block-video-skin .block-video-picker-menu {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
}
.block-video-skin .block-video-picker-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.375rem 0.5rem;
    background: transparent;
    border: 0;
    border-radius: 0.375rem;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background-color 120ms ease;
}
.block-video-skin .block-video-picker-item:hover,
.block-video-skin .block-video-picker-item:focus-visible {
    background-color: oklch(1 0 0 / 0.12);
    outline: none;
}
.block-video-skin .block-video-picker-item__check {
    width: 1rem;
    height: 1rem;
    flex: 0 0 auto;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1.5px currentColor;
    opacity: 0.4;
    position: relative;
}
.block-video-skin .block-video-picker-item[data-active] .block-video-picker-item__check {
    opacity: 1;
    background-color: currentColor;
}
.block-video-skin .block-video-picker-item[data-active] .block-video-picker-item__check::after {
    content: "";
    position: absolute;
    inset: 0.25rem;
    border-radius: 50%;
    background-color: var(--media-surface-background-color, oklch(0 0 0 / 0.6));
}
.block-video-skin .block-video-picker-item__label {
    flex: 1 1 auto;
    font-size: 0.875rem;
    line-height: 1.2;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}

/* "AI generated" pill. Rendered next to the language name for EN/ES in
   both pickers (audio + captions). Semi-transparent white on the dark
   popover surface so it reads as a subtle annotation, not a CTA. The
   sparkle SVG + "AI generated" microcopy matches the pattern users
   already associate with machine-generated content (e.g. Google,
   Notion). Font-size is proportional (em) so the tag scales with the
   label if it ever gets resized. */
.block-video-skin .block-video-ai-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.2em;
    padding: 0.5em 0.35em;
    background-color: oklch(1 0 0 / 0.15);
    border-radius: 0.1875rem;
    font-size: 0.525rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    line-height: 1;
    color: inherit;
    white-space: nowrap;
}
.block-video-skin .block-video-ai-tag__icon {
    width: 0.9em;
    height: 0.9em;
    flex: 0 0 auto;
}

/* Timed overlays (ACF-driven annotations). Each card positions itself
   absolutely inside <media-container> via a 9-point grid keyed off
   data-position. Color palette maps data-color → theme tokens so the
   cards stay visually consistent with the rest of the site. Visibility
   is class-driven (.is-visible) so the fade transition plays both on
   enter and on exit; the PHP `hidden` attribute is the pre-JS default
   and is removed on first reveal.

   Positioning uses inset-* properties relative to <media-container>.
   The bottom row sits ABOVE the controls safe-area so they never occlude
   the control bar. Top/center rows respect a symmetric 1rem pad. */
/* Base overlay: positioning + transition plumbing only. Visual styling
   (padding, background, font-size) lives on the per-template modifier
   below. Visibility is driven by `.is-visible` via the reveal engine
   (further down), NOT by `opacity: 0` here, so that slide/scale/fade
   can compose independently without conflicting with transform that
   positions center anchors. */
.block-video-skin .block-video-overlay {
    position: absolute;
    max-width: min(60%, 28rem);
    line-height: 1.25;
    text-wrap: balance;
    pointer-events: none;
    z-index: 5;
    /* bottom transition keeps cards in sync with the controls show/hide
       (--overlay-card-pad-bottom swaps between two clamp()ed values). */
    transition: bottom var(--media-controls-transition-duration) var(--media-controls-transition-timing-function, ease-out);
}

/* ---- Multi-language overlay text ----
   Each translatable element (tag text, product title/desc, catalyst role)
   is emitted as THREE sibling nodes with data-lang="pt|en|es". The player
   root carries data-active-lang; CSS hides the inactive siblings so
   swapping languages is a pure attribute flip (set by initCaptionsMenu in
   block-video.js when the user picks a caption track).

   `display: none` on the inactive ones (not visibility:hidden) because
   the tag pill is inline-flex and product/catalyst are flex columns —
   leaving empty nodes in would change layout width/height. */
.block-video-player[data-active-lang="pt"] .block-video-overlay [data-lang]:not([data-lang="pt"]),
.block-video-player[data-active-lang="en"] .block-video-overlay [data-lang]:not([data-lang="en"]),
.block-video-player[data-active-lang="es"] .block-video-overlay [data-lang]:not([data-lang="es"]) {
    display: none;
}

/* ---- Template: Tag ----
   Pill-shaped, uppercase text with optional inline icons (embedded via
   `[icon slug]` shortcodes parsed by bio_parse_icon_shortcodes). Color
   comes from data-color (black/sand/orange/dark-sand).

   Sizing strategy:
     - font-size is the only metric that scales with the VIDEO width
       (via `cqi`) — gives predictable global scaling with the player.
     - max-width caps at 40% of the parent overlay so the tag can never
       dominate the frame or collide with a centered catalyst card.
     - everything else (padding, border-radius, icon size) is in `em`
       so it stays proportional to the tag's OWN typography — the box
       feels correctly sized relative to itself no matter how much
       horizontal room it has. */
/* Outer wrapper is a 40% x 40% "tag zone" anchored at its `data-position`
   corner. Inside the zone, flex alignment places the pill wherever the
   editor wants — default inherits from Position (so a Position of
   bottom-right puts the pill at the bottom-right of the zone), but any
   explicit data-tag-alignment overrides. This gives editors room to
   compose the tag visually without fighting the Position system.

   Typography + color stay on the outer so `em` units inside the pill
   (padding/border-radius) resolve against the outer's font-size. */
.block-video-skin .block-video-overlay--tag {
    display: flex;
    width: 35%;
    /* border: 1px solid red; */
    height: 35%;
    max-width: 40%;
    font-size: 2.5cqi;
    line-height: 1.15;
    font-weight: 300;
    text-transform: uppercase;
    letter-spacing: 0.01em;
    color: var(--overlay-fg, var(--color-sand));
}

/* Default flex alignment: mirror the overlay Position so the pill lands
   at the matching corner of the zone. Overridden by any
   `[data-tag-alignment]` block below. */
.block-video-skin .block-video-overlay--tag[data-position="top-left"]      { justify-content: flex-start; align-items: flex-start; }
.block-video-skin .block-video-overlay--tag[data-position="top-center"]    { justify-content: center;     align-items: flex-start; }
.block-video-skin .block-video-overlay--tag[data-position="top-right"]     { justify-content: flex-end;   align-items: flex-start; }
.block-video-skin .block-video-overlay--tag[data-position="center-left"]   { justify-content: flex-start; align-items: center; }
.block-video-skin .block-video-overlay--tag[data-position="center"]        { justify-content: center;     align-items: center; }
.block-video-skin .block-video-overlay--tag[data-position="center-right"]  { justify-content: flex-end;   align-items: center; }
.block-video-skin .block-video-overlay--tag[data-position="bottom-left"]   { justify-content: flex-start; align-items: flex-end; }
.block-video-skin .block-video-overlay--tag[data-position="bottom-center"] { justify-content: center;     align-items: flex-end; }
.block-video-skin .block-video-overlay--tag[data-position="bottom-right"]  { justify-content: flex-end;   align-items: flex-end; }

/* Explicit alignment overrides. Declared AFTER the defaults so equal
   specificity (both attribute selectors with the same wrapper path)
   resolves by source order. */
.block-video-skin .block-video-overlay--tag[data-tag-alignment="top-left"]      { justify-content: flex-start; align-items: flex-start; }
.block-video-skin .block-video-overlay--tag[data-tag-alignment="top-center"]    { justify-content: center;     align-items: flex-start; }
.block-video-skin .block-video-overlay--tag[data-tag-alignment="top-right"]     { justify-content: flex-end;   align-items: flex-start; }
.block-video-skin .block-video-overlay--tag[data-tag-alignment="middle-left"]   { justify-content: flex-start; align-items: center; }
.block-video-skin .block-video-overlay--tag[data-tag-alignment="middle-center"] { justify-content: center;     align-items: center; }
.block-video-skin .block-video-overlay--tag[data-tag-alignment="middle-right"]  { justify-content: flex-end;   align-items: center; }
.block-video-skin .block-video-overlay--tag[data-tag-alignment="bottom-left"]   { justify-content: flex-start; align-items: flex-end; }
.block-video-skin .block-video-overlay--tag[data-tag-alignment="bottom-center"] { justify-content: center;     align-items: flex-end; }
.block-video-skin .block-video-overlay--tag[data-tag-alignment="bottom-right"]  { justify-content: flex-end;   align-items: flex-end; }

/* Inner pill: sizes to its content (inline-flex) but capped at 100%
   of the zone. When the text is long the pill wraps and stays inside
   the zone boundary. */
.block-video-skin .block-video-overlay--tag .block-video-overlay__reveal {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    max-width: 100%;
    padding: 0.35em 0.75em;
    border-radius: 0.3em;
    background-color: var(--overlay-bg, var(--color-orange));
}

/* Inline icons emitted by bio_parse_icon_shortcodes() inside the tag
   text. Same markup the quote block uses (.quote-icon-inline for
   standalone icons, .quote-icon-word for [icon]text[/icon] attached
   form), but we re-declare the sizing here so the SVG sits inline at
   ~1em — without these rules the raw SVG renders at its intrinsic
   viewBox size and breaks out of the pill. */
.block-video-skin .block-video-overlay--tag .quote-icon-inline {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1em;
    height: 1em;
    vertical-align: -0.125em;
    /* No start-margin: icon flush against the pill edge or preceding text's
       own spacing. Small end-margin separates it from following text.
       (margin-left / :first-child hacks don't work reliably against
       margin-inline because logical and physical properties are different
       CSS properties — whichever is declared last in source wins regardless
       of specificity. Avoiding start margin entirely is cleaner.) */
    margin-inline-start: 0;
    margin-inline-end: 0.3em;
    flex: 0 0 auto;
    overflow: hidden;
}


.block-video-skin .block-video-overlay--tag .quote-icon-word {
    position: relative;
    padding-left: 1.2em;
    display: inline;
}
.block-video-skin .block-video-overlay--tag .quote-icon-word .quote-icon-glyph {
    position: absolute;
    left: 0.05em;
    top: 0.15em;
    width: 1em;
    height: 1em;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.block-video-skin .block-video-overlay--tag .quote-icon-inline svg,
.block-video-skin .block-video-overlay--tag .quote-icon-word .quote-icon-glyph svg {
    width: 100%;
    height: 100%;
    display: block;
    fill: currentColor;
}

/* Color variants (Tag only). Pairings chosen to maintain legibility. */
.block-video-skin .block-video-overlay[data-color="black"] {
    --overlay-bg: var(--color-black);
    --overlay-fg: var(--color-sand);
}
.block-video-skin .block-video-overlay[data-color="sand"] {
    --overlay-bg: var(--color-sand);
    --overlay-fg: var(--color-black);
}
.block-video-skin .block-video-overlay[data-color="orange"] {
    --overlay-bg: var(--color-orange);
    --overlay-fg: var(--color-sand);
}
.block-video-skin .block-video-overlay[data-color="dark-sand"] {
    --overlay-bg: var(--color-dark-sand);
    --overlay-fg: var(--color-sand);
}

/* ---- Template: Product ----
   Vertical card: thumbnail on top, product title below, optional short
   description. Sand background, black fg. Width is cqi-scaled so it
   stays readable in small containers. */
/* Outer wrapper: sizing only. Visual chrome (flex stack, padding, bg,
   border-radius) moved to inner __reveal. */
.block-video-skin .block-video-overlay--product {
    width: min(28%, 16rem);
    max-width: none;
    color: var(--color-black);
}
.block-video-skin .block-video-overlay--product .block-video-overlay__reveal {
    display: flex;
    flex-direction: column;
    gap: clamp(0.4rem, 0.8cqi, 0.625rem);
    padding: clamp(0.5rem, 1.2cqi, 0.875rem);
    width: 100%;
    background: var(--color-sand);
    border-radius: clamp(0.5rem, 1cqi, 0.875rem);
}
.block-video-skin .block-video-overlay--product .block-video-overlay__thumb {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: clamp(0.25rem, 0.6cqi, 0.5rem);
    display: block;
}
.block-video-skin .block-video-overlay--product .block-video-overlay__title {
    font-size: clamp(0.875rem, 1.6cqi, 1.125rem);
    font-weight: 600;
    line-height: 1.2;
}
.block-video-skin .block-video-overlay--product .block-video-overlay__desc {
    margin: 0;
    font-size: clamp(0.75rem, 1.2cqi, 0.875rem);
    line-height: 1.35;
    text-wrap: balance;
}

/* ---- Template: Catalyst ----
   Horizontal card: avatar on the left, name/role/biomes stacked on the
   right. Dark-sand background, sand fg (inverse of Product).
   Biomes row shows the first name + overflow count chip, mirroring the
   product-biomes.php pattern but compact. */
/* Outer wrapper: sizing only. Visual chrome moved to inner __reveal. */
.block-video-skin .block-video-overlay--catalyst {
    width: min(32%, 20rem);
    max-width: none;
    color: var(--color-sand);
}
.block-video-skin .block-video-overlay--catalyst .block-video-overlay__reveal {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: clamp(0.5rem, 1.2cqi, 0.875rem);
    padding: clamp(0.5rem, 1.2cqi, 0.875rem);
    width: 100%;
    background: var(--color-dark-sand);
    border-radius: clamp(0.5rem, 1cqi, 0.875rem);
}
.block-video-skin .block-video-overlay--catalyst .block-video-overlay__avatar {
    flex: 0 0 auto;
    width: clamp(2.5rem, 5cqi, 3.75rem);
    aspect-ratio: 1;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}
.block-video-skin .block-video-overlay--catalyst .block-video-overlay__info {
    display: flex;
    flex-direction: column;
    gap: 0.15em;
    min-width: 0;
}
.block-video-skin .block-video-overlay--catalyst .block-video-overlay__name {
    font-size: clamp(0.875rem, 1.5cqi, 1rem);
    font-weight: 600;
    line-height: 1.2;
}
.block-video-skin .block-video-overlay--catalyst .block-video-overlay__role {
    font-size: clamp(0.75rem, 1.1cqi, 0.875rem);
    opacity: 0.75;
    line-height: 1.2;
}
.block-video-skin .block-video-overlay--catalyst .block-video-overlay__biomes {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: 0.25rem;
    font-size: clamp(0.65rem, 0.95cqi, 0.75rem);
    opacity: 0.75;
}
.block-video-skin .block-video-overlay--catalyst .block-video-overlay__biome-more {
    opacity: 0.8;
}

/* ---- Reveal engine (v2) ----
   Three disjoint states represented by class presence:

     pre-entry  (no class) → translate/scale at slide-in offsets, opacity 0
     visible    (.is-visible) → translate 0 0, scale 1, opacity 1
     exiting    (.is-exiting) → translate/scale at slide-out offsets, opacity 0

   Why three instead of two: the hidden state has to carry DIFFERENT
   translate/scale values depending on whether the element is about to
   enter (uses slide-in) or just exited (uses slide-out). Encoding them
   both into the same "not visible" state with a single `.is-exiting`
   toggle wouldn't work because of how the browser coalesces class
   changes — when JS does `remove('.is-exiting'); add('.is-visible')`
   in the same tick to re-enter after a dry exit, the translate diff
   collapses (exit=0,0 → visible=0,0, no motion to animate). The fix:
   JS removes `.is-exiting` automatically 320ms after an exit (via a
   timer), settling the element into pre-entry state. Next `.is-visible`
   then animates cleanly from slide-in offset → 0,0 with no hacks.

   Opacity is ALSO decoupled: it only transitions when fade is
   explicitly configured (via `[data-reveal-in~="fade"]` /
   `[data-reveal-out~="fade"].is-exiting`). Otherwise it snaps. This
   kills the "looks like fade even though I only asked for slide" bug. */

/* Pre-entry baseline — OUTER owns translate/opacity/visibility only. */
.block-video-skin .block-video-overlay {
    opacity: 0;
    visibility: hidden;
    translate: var(--slide-in-x, 0) var(--slide-in-y, 0);
}

/* Visible state — always pristine regardless of reveal config. */
.block-video-skin .block-video-overlay.is-visible {
    opacity: 1;
    visibility: visible;
    translate: 0 0;
}

/* Exit state — OUTER carries slide-out offsets only; scale lives on inner.
   Declared AFTER `.is-visible` so simultaneous class flips resolve here. */
.block-video-skin .block-video-overlay.is-exiting {
    opacity: 0;
    visibility: hidden;
    translate: var(--slide-out-x, 0) var(--slide-out-y, 0);
}

/* ---- Inner reveal wrapper: scale + transform-origin live here ----
   Why an inner wrapper: the outer .block-video-overlay carries
   centering `transform: translateX/Y(-50%)` for positions like
   top-center / middle-left / center. `transform-origin` percentages
   resolve against the LAYOUT box (pre-transform), so putting scale on
   the outer made every pivot drift in the direction of the centering
   offset — no amount of calc() correction produces a clean result
   across all three animation axes simultaneously.

   The `__reveal` inner has NO positioning transforms, so its
   `transform-origin` percentages map 1:1 to its own visual border-box.
   The inner also owns `overflow: hidden`, which sets up future
   wipe/clip effects (max-width/max-height reveals) without affecting
   positioning. */
.block-video-skin .block-video-overlay__reveal {
    scale: 1;
    overflow: hidden;
}

/* Scale-in: inner fully collapses (0 → 1). */
.block-video-skin .block-video-overlay[data-reveal-in~="scale"] .block-video-overlay__reveal {
    scale: 0;
}
/* Scale-out: inner collapses back (1 → 0) when exiting. Declared AFTER
   the pre-entry rule so `.is-exiting` wins when both attrs are set. */
.block-video-skin .block-video-overlay.is-exiting[data-reveal-out~="scale"] .block-video-overlay__reveal {
    scale: 0;
}
/* Visible: inner restored to 1 regardless of other attrs. Declared last
   so it wins when `.is-visible` is added. */
.block-video-skin .block-video-overlay.is-visible .block-video-overlay__reveal {
    scale: 1;
}

/* Scale pivot — transform-origin on the inner, simple percentages.
   Direct mapping: no correction math needed because the inner has no
   centering transform interfering with layout coordinates. Default
   (no data-scale-origin) is CSS initial value = 50% 50% = visual center. */
.block-video-skin .block-video-overlay[data-scale-origin="top-left"]      .block-video-overlay__reveal { transform-origin: 0%   0%;   }
.block-video-skin .block-video-overlay[data-scale-origin="top-center"]    .block-video-overlay__reveal { transform-origin: 50%  0%;   }
.block-video-skin .block-video-overlay[data-scale-origin="top-right"]     .block-video-overlay__reveal { transform-origin: 100% 0%;   }
.block-video-skin .block-video-overlay[data-scale-origin="middle-left"]   .block-video-overlay__reveal { transform-origin: 0%   50%;  }
.block-video-skin .block-video-overlay[data-scale-origin="middle-right"]  .block-video-overlay__reveal { transform-origin: 100% 50%;  }
.block-video-skin .block-video-overlay[data-scale-origin="bottom-left"]   .block-video-overlay__reveal { transform-origin: 0%   100%; }
.block-video-skin .block-video-overlay[data-scale-origin="bottom-center"] .block-video-overlay__reveal { transform-origin: 50%  100%; }
.block-video-skin .block-video-overlay[data-scale-origin="bottom-right"]  .block-video-overlay__reveal { transform-origin: 100% 100%; }

/* Inner scale transition — only armed when scale is configured. */
.block-video-skin .block-video-overlay[data-reveal-in~="scale"] .block-video-overlay__reveal,
.block-video-skin .block-video-overlay[data-reveal-out~="scale"] .block-video-overlay__reveal {
    transition: scale 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* ---- Outer transition suite ----
   Outer now animates only translate (+ bottom for controls sync).
   Scale moved to inner, so it's no longer in the outer transition list.
   Opacity stays off by default and joins only when fade is configured. */
.block-video-skin .block-video-overlay[data-reveal-in],
.block-video-skin .block-video-overlay[data-reveal-out],
.block-video-skin .block-video-overlay[data-slide-in-direction],
.block-video-skin .block-video-overlay[data-slide-out-direction] {
    transition-property: translate, bottom;
    transition-duration: 260ms, var(--media-controls-transition-duration);
    transition-timing-function:
        cubic-bezier(0.22, 1, 0.36, 1),
        var(--media-controls-transition-timing-function, ease-out);
}

/* Fade-IN: opacity joins the outer transition on entry. */
.block-video-skin .block-video-overlay[data-reveal-in~="fade"] {
    transition-property: translate, opacity, bottom;
    transition-duration: 260ms, 260ms, var(--media-controls-transition-duration);
    transition-timing-function:
        cubic-bezier(0.22, 1, 0.36, 1),
        cubic-bezier(0.22, 1, 0.36, 1),
        var(--media-controls-transition-timing-function, ease-out);
}

/* Fade-OUT: opacity animates during exit; visibility is delayed so the
   node stays rendered throughout the fade. Scoped to `.is-exiting`. */
.block-video-skin .block-video-overlay[data-reveal-out~="fade"].is-exiting {
    transition-property: translate, opacity, visibility, bottom;
    transition-duration: 260ms, 260ms, 260ms, var(--media-controls-transition-duration);
    transition-delay: 0s, 0s, 260ms, 0s;
    transition-timing-function:
        cubic-bezier(0.22, 1, 0.36, 1),
        cubic-bezier(0.22, 1, 0.36, 1),
        linear,
        var(--media-controls-transition-timing-function, ease-out);
}

/* "Cinematic" exit without fade: when slide-out or scale-out is set but
   fade-out is NOT, keep the outer fully opaque while the movement plays
   (on inner for scale, on outer for slide), then snap opacity/visibility
   to hidden at the end. The inner's own scale transition (armed above)
   carries the motion; the outer just holds opacity steady for 260ms
   before hard-cutting to hidden. */
.block-video-skin .block-video-overlay.is-exiting[data-slide-out-direction]:not([data-reveal-out~="fade"]),
.block-video-skin .block-video-overlay.is-exiting[data-reveal-out~="scale"]:not([data-reveal-out~="fade"]) {
    transition-property: translate, opacity, visibility, bottom;
    transition-duration: 260ms, 0ms, 0ms, var(--media-controls-transition-duration);
    transition-delay: 0s, 260ms, 260ms, 0s;
    transition-timing-function:
        cubic-bezier(0.22, 1, 0.36, 1),
        linear,
        linear,
        var(--media-controls-transition-timing-function, ease-out);
}
/* SLIDE — 8-point compass. Direction names refer to the START POINT of
   entry (slide-in-direction = 'top' means the card enters FROM the top)
   and the END POINT of exit (slide-out-direction = 'right' means the
   card leaves TOWARD the right). Uses the `translate:` individual
   property so it composes cleanly with `transform: translate(-50%, …)`
   used by centered positions without fighting it. */
.block-video-skin .block-video-overlay[data-slide-in-direction="top"]          { --slide-in-x:   0;   --slide-in-y: -30%; }
.block-video-skin .block-video-overlay[data-slide-in-direction="top-right"]    { --slide-in-x:  30%; --slide-in-y: -30%; }
.block-video-skin .block-video-overlay[data-slide-in-direction="right"]        { --slide-in-x:  30%; --slide-in-y:  0;   }
.block-video-skin .block-video-overlay[data-slide-in-direction="bottom-right"] { --slide-in-x:  30%; --slide-in-y:  30%; }
.block-video-skin .block-video-overlay[data-slide-in-direction="bottom"]       { --slide-in-x:   0;  --slide-in-y:  30%; }
.block-video-skin .block-video-overlay[data-slide-in-direction="bottom-left"]  { --slide-in-x: -30%; --slide-in-y:  30%; }
.block-video-skin .block-video-overlay[data-slide-in-direction="left"]         { --slide-in-x: -30%; --slide-in-y:  0;   }
.block-video-skin .block-video-overlay[data-slide-in-direction="top-left"]     { --slide-in-x: -30%; --slide-in-y: -30%; }

.block-video-skin .block-video-overlay[data-slide-out-direction="top"]          { --slide-out-x:   0;   --slide-out-y: -30%; }
.block-video-skin .block-video-overlay[data-slide-out-direction="top-right"]    { --slide-out-x:  30%; --slide-out-y: -30%; }
.block-video-skin .block-video-overlay[data-slide-out-direction="right"]        { --slide-out-x:  30%; --slide-out-y:  0;   }
.block-video-skin .block-video-overlay[data-slide-out-direction="bottom-right"] { --slide-out-x:  30%; --slide-out-y:  30%; }
.block-video-skin .block-video-overlay[data-slide-out-direction="bottom"]       { --slide-out-x:   0;  --slide-out-y:  30%; }
.block-video-skin .block-video-overlay[data-slide-out-direction="bottom-left"]  { --slide-out-x: -30%; --slide-out-y:  30%; }
.block-video-skin .block-video-overlay[data-slide-out-direction="left"]         { --slide-out-x: -30%; --slide-out-y:  0;   }
.block-video-skin .block-video-overlay[data-slide-out-direction="top-left"]     { --slide-out-x: -30%; --slide-out-y: -30%; }

/* Position vars on the SKIN ROOT so all children inherit.
   Two independent systems:
     --overlay-pad-bottom  → captions (centered, just vertical travel)
     --overlay-card-pad / --overlay-card-pad-bottom → annotation cards (all 4 edges)
   Keeping them separate lets each be tuned without affecting the other. */
.block-video-skin {
    /* Captions vertical travel */
    --overlay-pad-bottom: clamp(0.625rem, 2cqi, 1.25rem);

    /* Overlay cards: uniform inset from all container edges (~5% of width) */
    --overlay-card-pad: 5cqi;
    --overlay-card-pad-bottom: calc(var(--overlay-card-pad) + 40px);
}

/* When controls are visible: captions ride up with them; overlay bottom
   cards add the controls bar height on top of the card inset. */
.block-video-skin:has(.media-controls[data-visible]) {
    --overlay-pad-bottom: clamp(4rem, 9cqi, 4.5rem);
    /* --overlay-card-pad-bottom: calc(var(--overlay-card-pad) + 40px); */
}

.block-video-skin .block-video-overlay[data-position="top-left"] {
    top: var(--overlay-card-pad); left: var(--overlay-card-pad);
}
.block-video-skin .block-video-overlay[data-position="top-center"] {
    top: var(--overlay-card-pad); left: 50%; transform: translateX(-50%);
}
.block-video-skin .block-video-overlay[data-position="top-right"] {
    top: var(--overlay-card-pad); right: var(--overlay-card-pad);
}
.block-video-skin .block-video-overlay[data-position="center-left"] {
    top: 50%; left: var(--overlay-card-pad); transform: translateY(-50%);
}
.block-video-skin .block-video-overlay[data-position="center"] {
    top: 50%; left: 50%; transform: translate(-50%, -50%);
    text-align: center;
}
.block-video-skin .block-video-overlay[data-position="center-right"] {
    top: 50%; right: var(--overlay-card-pad); transform: translateY(-50%);
}
.block-video-skin .block-video-overlay[data-position="bottom-left"] {
    bottom: var(--overlay-card-pad-bottom); left: var(--overlay-card-pad);
}
.block-video-skin .block-video-overlay[data-position="bottom-center"] {
    bottom: var(--overlay-card-pad-bottom); left: 50%; transform: translateX(-50%);
}
.block-video-skin .block-video-overlay[data-position="bottom-right"] {
    bottom: var(--overlay-card-pad-bottom); right: var(--overlay-card-pad);
}

/* ---- DOM caption renderer ----
   Receives VTT cue text written by JS (track.mode='hidden' + cuechange).
   Sits at z-index 6 — above overlays (5), below controls (10). Anchored
   to --overlay-pad-bottom so it travels with the control bar show/hide
   animation at the same duration/easing. Founders Grotesk is inherited
   from .block-video-skin. */
.block-video-skin .block-video-captions {
    position: absolute;
    left: 50%;
    bottom: var(--overlay-pad-bottom);
    translate: -50% 0;
    z-index: 6;
    width: max-content;
    max-width: min(80%, 40rem);
    text-align: center;
    text-wrap: balance;
    font-size: clamp(0.875rem, 2cqi, 1.125rem);
    line-height: 1.4;
    color: var(--color-sand);
    background: oklch(0 0 0 / 0.72);
    padding: clamp(0.2rem, 0.8cqi, 0.4rem) clamp(0.4rem, 1.5cqi, 0.9rem);
    border-radius: clamp(0.25rem, 0.6cqi, 0.5rem);
    pointer-events: none;
    transition: bottom var(--media-controls-transition-duration) var(--media-controls-transition-timing-function, ease-out);
}

.block-video-skin .block-video-captions:empty {
    display: none;
}

/* Fallback override for native WebKit caption positioning when DOM renderer
   is unavailable — forces single-row lift regardless of player width. */
.block-video-skin:has(.media-controls[data-visible]) {
    --media-caption-track-y: -3.5rem;
}
