/* =========================================================================
   Video — product demo player with minimal controls.

   Three shapes, set in the content rather than in CSS:

     ambient    autoplay + muted + loop + no control bar. A silent demo loop
                that behaves like a moving screenshot. Click toggles it.
     bare       normal playback, no control bar. Click toggles.
     default    poster, centre play button, and a slim bar: play/pause,
                scrubber, time, mute (only when the file has sound), full
                screen.

   The native control bar is never used — it is a different size, shape and
   colour in every browser, which on a page of six demos reads as six
   different players.

   Behaviour: assets/js/video.js. Markup: video() in scripts/study.mjs.
   ========================================================================= */

/* ONE radius for every video on the site, hero or nested, in a row or in a
   carousel. `--tile-radius` (18px) is right at card size and reads as flat on
   a 1318px hero, which is why the hero used to carry a 32px override — but
   that put two corner languages on the same page, and a reader sees the demos
   and the hero as the same kind of object. The larger corner is the one that
   survives at both sizes, so every frame takes it.

   Declared on `.vid` rather than `:root` because it is the video component's
   own measure: nothing else on the site should inherit it by accident, and
   there is still exactly one place to change it. */
.vid { --vid-radius: 32px; margin: 0; position: relative; }

.vid__frame {
  position: relative; overflow: hidden;
  border-radius: var(--vid-radius);
  /* The frame's shape and its fill are independent of the video's own
     proportions, so a near-square clip can sit centred on a wide band of
     colour rather than being cropped to fit it. */
  background: var(--vid-bg, #000);
  aspect-ratio: var(--vid-ratio, 16 / 9);
  /* A mat rather than a shadow. The demos are screen recordings that already
     carry their own light ground, so a drop shadow was lifting a rectangle
     that has no edge of its own to cast one — the white border states the
     boundary instead of implying it.

     It sits OUTSIDE the padding box, which is what `inset: 0` on the video
     resolves against, so the clip is inset by it with nothing else to change. */
  /* audit-ok: a 5px mat, asked for directly — it is a border weight, not a
     step on the spacing scale, and it must not scale with the viewport. */
  border: 5px solid var(--card);
}
/* The video always sits inside this plain wrapper div, never directly inside
   `.vid__frame` — including the "ordinary" case, where the clip fills the
   frame exactly and looks redundant on paper. It isn't: a `<video>`'s decoded
   frame is composited on its own layer in this engine, and that layer does
   not reliably respect an `overflow`/`border-radius` declared on its own
   parent when the video is a DIRECT child of the box doing the clipping —
   confirmed here the same way as the inlaid case below, with a 6px outline
   inset into the frame's curve: the outline rounded, the video's own corner
   sat square inside it, overlapping ground the curve should have hidden. A
   second, uninvolved div between the two clips reliably, every time. */
.vid__frame .vid__clip {
  position: absolute; inset: 0; overflow: hidden;
  border-radius: var(--vid-radius);
}
.vid__frame .vid__clip video {
  position: absolute; inset: 0;
  width: 100%; height: 100%; display: block;
  /* `contain` letterboxes onto --vid-bg and is centred by default; `cover`
     crops to fill. Set per instance via --vid-fit. */
  object-fit: var(--vid-fit, cover);
  object-position: center;
  /* The video carries the radius as well as its wrapper. Defensive rather
     than load-bearing: an element's own `border-radius` clips its own layer,
     so it holds even where a composited video might otherwise paint through
     an ancestor's rounded `overflow`. Safe because the video box is always
     exactly the clip box — `inset: 0` here, `100%/100%` when inlaid — so the
     two curves land on the same edge rather than fighting.

     IT CANNOT SAVE A MISDECLARED CLIP, and that is the failure worth knowing
     about. With `fit: contain`, if a video's declared `w`/`h` do not match the
     real file, the content letterboxes inside the element: the painted
     rectangle sits inside the rounded corners, the radius clips nothing, and
     the content's own square corners are what the reader sees. The warehousing
     hero declared 1664x1080 for a 1784x1080 file and drew 24px transparent
     bands top and bottom — no amount of radius here fixes that. Declare the
     file's real dimensions. */
  border-radius: var(--vid-radius);
}

/* ---- full screen
   Without this the button appeared to do nothing. The request WAS being made
   and granted; the frame simply kept the shape it has on the page. Fullscreen
   hands an element the whole screen, but an author `aspect-ratio` still
   constrains the box, so the frame drew itself at 16/9 in the middle of a
   black screen, rounded corners and 5px mat included, at close to the size it
   had a moment earlier. Nothing looked like it had happened.

   Everything that makes the frame a figure on a page comes off here: it is
   not sitting on the page any more, so it should not be wearing the page's
   shape. `contain` rather than the instance's own fit, because a screen is
   rarely the clip's proportions and cropping the thing someone just asked to
   see in full is the wrong way round. */
.vid__frame:fullscreen {
  aspect-ratio: auto;
  width: 100%; height: 100%;
  border: 0; border-radius: 0;
  background: #000;
}
.vid__frame:fullscreen .vid__clip {
  position: static; inset: auto; margin: 0;
  width: 100%; height: 100%;
  max-width: none; max-height: none;
  aspect-ratio: auto; border-radius: 0;
}
/* The radius comes off the video with everything else — it now carries its
   own, so zeroing only the wrapper would leave a rounded clip on a screen
   that has no page shape to echo. */
.vid__frame:fullscreen .vid__clip video { object-fit: contain; border-radius: 0; }

.vid__cap {
  margin-top: var(--space-xs);
  font-size: var(--type-caption-size); line-height: var(--type-caption-line);
  color: var(--ink-faint);
}

/* ------------------------------ big play -------------------------------
   The poster-state affordance. Hidden once playback starts, and absent
   entirely for an ambient loop, which never waits for permission. */
/* No scrim. It existed to lift a white button off arbitrary footage, and the
   button is dark now — dimming the whole first frame to introduce a video is
   a cost paid by the content to solve a problem the button no longer has. */
.vid__big {
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-items: center;
  border: 0; padding: 0; cursor: pointer;
  background: transparent;
  transition: opacity .25s ease, background .25s ease;
}
.vid__big span {
  width: 68px; height: 68px; border-radius: var(--radius-round);
  display: grid; place-items: center;
  /* Dark on light, which is the way round these demos actually run: every
     one is a screen recording of a light interface. */
  background: var(--ink); color: var(--card);
  box-shadow: var(--shadow-onmedia-hi);
  transition: transform .2s ease;
}
.vid__big:hover span { transform: scale(1.06); }
/* Dark too, and for the same reason — a white focus ring was legible against
   the scrim that is now gone, and would vanish on a light first frame. */
.vid__big:focus-visible { outline: 2px solid var(--ink); outline-offset: -4px; }
.vid.is-playing .vid__big { opacity: 0; pointer-events: none; }

/* ------------------------------- controls -------------------------------
   Sits over the video on a gradient rather than below it, so the player's
   footprint is exactly the video's box and a row of demos stays on a grid. */
.vid__bar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  display: flex; align-items: center; gap: var(--space-xs);
  padding: var(--space-m) var(--space-s) var(--space-xs);
  background: linear-gradient(to top, rgb(0 0 0 / .68), transparent);
  /* `pointer-events` tracks opacity, and it is not optional. A bar at zero
     opacity still takes clicks, so an invisible one would swallow every press
     along the bottom edge of the frame — including the frame's own
     click-to-play, which bails out when the press lands on a control. */
  opacity: 0; pointer-events: none; transition: opacity .2s ease;
}
/* The bar appears only while the video is RUNNING and the pointer is moving
   over it, then hides itself again. Three states, and each one is deliberate:

     never played   nothing. A still with a play button on it; a control bar
                    would be chrome explaining an interaction not yet begun.
     playing        on pointer movement, auto-hiding after the movement stops.
                    A demo loop is the content, and a bar parked over its
                    bottom edge is covering the thing it exists to serve.
     paused         nothing again. The big play button is already the state,
                    and two controls saying "paused" is one too many.

   `is-cursor` is set by video.js on pointermove and cleared on a timer.
   Focus is the exception that keeps this operable: a keyboard reader has no
   pointer to move, so `:focus-within` shows the bar and holds it there. */
.vid.is-started.is-playing.is-cursor .vid__bar,
.vid.is-started:focus-within .vid__bar { opacity: 1; pointer-events: auto; }

.vid__btn {
  flex: none; width: 32px; height: 32px; padding: 0; border: 0; border-radius: var(--radius-round);
  display: grid; place-items: center;
  background: transparent; color: #fff; cursor: pointer;
}
.vid__btn:hover { background: rgb(255 255 255 / .18); }
.vid__btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* ---- two-state controls
   Each of these buttons ships both of its glyphs and CSS shows one. The
   player used to write a `data-state` attribute that nothing in here read, so
   the play button kept its triangle while the clip ran, the mute button kept
   its speaker while muted, and the only thing that actually changed state was
   the aria-label. Grid, not block, because .vid__btn centres its child with
   place-items and an inline span would sit off-centre. */
.vid__ico { display: grid; place-items: center; }
.vid__ico--pause, .vid__ico--muted, .vid__ico--fsexit { display: none; }

.vid.is-playing .vid__ico--play { display: none; }
.vid.is-playing .vid__ico--pause { display: grid; }

/* `icon()` writes state 'on' when sound is audible, so 'off' is muted. */
[data-vid-mute][data-state="off"] .vid__ico--sound { display: none; }
[data-vid-mute][data-state="off"] .vid__ico--muted { display: grid; }

.vid.is-full .vid__ico--fs { display: none; }
.vid.is-full .vid__ico--fsexit { display: grid; }

/* Scrubber — a real range input, so dragging, arrow keys, Home/End and the
   platform's own touch handling all come for free. */
.vid__seek {
  flex: 1 1 auto; min-width: 0; height: 20px;
  -webkit-appearance: none; appearance: none;
  background: transparent; cursor: pointer;
}
.vid__seek::-webkit-slider-runnable-track {
  height: 4px; border-radius: var(--radius-pill);
  background: linear-gradient(to right,
    #fff var(--vid-progress, 0%), rgb(255 255 255 / .3) var(--vid-progress, 0%));
}
.vid__seek::-moz-range-track { height: 4px; border-radius: var(--radius-pill); background: rgb(255 255 255 / .3); }
.vid__seek::-moz-range-progress { height: 4px; border-radius: var(--radius-pill); background: #fff; }
.vid__seek::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 12px; height: 12px; border-radius: var(--radius-round); background: #fff; border: 0;
  margin-top: calc(-1 * var(--space-3xs));
}
.vid__seek::-moz-range-thumb { width: 12px; height: 12px; border-radius: var(--radius-round); background: #fff; border: 0; }
.vid__seek:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

.vid__time {
  flex: none; font-size: 12px; font-weight: 600; color: #fff;
  font-variant-numeric: tabular-nums; letter-spacing: .01em;
}

/* An ambient loop has no bar and no poster button; the whole frame is the
   toggle, so it gets the pointer. */
.vid--ambient .vid__frame { cursor: pointer; }

/* ------------------------------- inlaid ---------------------------------
   A clip whose frame is a different shape from itself. Instead of filling the
   frame and letterboxing inside it, the element box is sized to the CLIP's own
   proportions and centred.

   That distinction is the whole point: with object-fit the element still fills
   the frame, so a border-radius would round the FRAME's corners and leave the
   video's own edges square. Sizing the box to the clip puts the radius on the
   video's real edges, and it reads as a panel set into the ground rather than
   a rectangle laid on top of it. */
/* The mat. Without it the clip touches the frame's top and bottom edges and
   reads as a rectangle laid over a coloured band; with it there is ground on
   all four sides, like a photo in a frame.

   Padding on the frame does NOT do this. An absolutely positioned child's
   containing block is its ancestor's PADDING box, so `inset: 0` lands on the
   frame's outer edge and the padding is simply ignored. The mat has to come
   off the clip's own maximum size instead — `margin: auto` then centres what
   is left, which keeps the four margins symmetrical for free.

   This sizing lives on the WRAPPER (`.vid__clip`) around the `<video>`, not on
   the video itself, and the wrapper is load-bearing sitewide, not just here —
   see the note on `.vid__frame .vid__clip` above for what happens without it.
   The extra rules below are the inlaid shape on top of that: sized to the
   clip's own proportions and centred with a mat, rather than filling the
   frame edge to edge.

   The video inside the clip is inert: no radius, no positioning of its own,
   just 100%/100% of a box its wrapper already sized and rounded. */
.vid--inlaid .vid__frame .vid__clip {
  position: absolute; inset: 0; margin: auto; overflow: hidden;
  width: auto; height: auto;
  max-width: calc(100% - 2 * var(--vid-inlay, clamp(1rem, 3vh, 2.25rem)));
  max-height: calc(100% - 2 * var(--vid-inlay, clamp(1rem, 3vh, 2.25rem)));
  aspect-ratio: var(--vid-natural);
  border-radius: var(--vid-radius);
}
/* The video still fills its wrapper exactly, so it keeps the wrapper's curve
   on its own layer — see the note on `.vid__frame .vid__clip video`. This is
   the case that exposed the bug: an inlaid clip sits on open ground, so a
   square corner here shows against the gradient rather than against more
   video. */
.vid--inlaid .vid__frame .vid__clip video {
  position: static; inset: auto; margin: 0;
  width: 100%; height: 100%;
  max-width: none; max-height: none;
  aspect-ratio: auto; border-radius: var(--vid-radius);
}

/* Below this width a frame shaped differently from its clip costs more than it
   buys, so the frame takes the clip's own proportions. The video is still
   never cropped — the ground simply goes away because there is nothing left to
   pad, and with it the radius has nothing to sit against. */
@media (max-width: 640px) {
  /* Frame takes the clip's own shape here, so there is no ground left to mat
     against — an inset would just shrink the clip and reintroduce bands. */
  .vid--inlaid .vid__frame { aspect-ratio: var(--vid-natural); }
  .vid--inlaid .vid__frame .vid__clip { max-width: 100%; max-height: 100%; }
  /* The video carries its own radius now, so it has to be zeroed alongside
     the wrapper or the clip would keep a curve the frame no longer has. */
  .vid--inlaid .vid__frame .vid__clip,
  .vid--inlaid .vid__frame .vid__clip video { border-radius: 0; }
}

/* The hero's 32px override lived here. It is now `--vid-radius` on `.vid` at
   the top of this file, because every video wears it — see the note there.

   This rule stays: below the tier a hero's frame takes the clip's own shape,
   so the clip fills it edge to edge and has no ground left to round against. */
@media (max-width: 640px) {
  .study-hero .vid__frame .vid__clip,
  .study-hero .vid__frame .vid__clip video { border-radius: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .vid__big, .vid__big span, .vid__bar { transition: none; }
}
