Customize skins
Learn how to customize Video.js v10 skins by copying and modifying them
Video.js v10 comes with two pre-built skins; Default and Minimal. Basic customization is possible with CSS custom properties, but if you want more control over the design and functionality of your player, you can copy the skin’s code into your project and modify it as needed. We call this “ejecting” the skin, since you’re taking the internal code that makes up the skin and making it your own.
Basic customization
| Property Name | Description | Type | Example |
|---|---|---|---|
--media-border-radius
|
The border radius of the media player | A valid border-radius value |
1rem
|
--media-color-primary
|
The color of icons and text in media controls |
<color>
|
red
|
You can of course also add your own classnames to the skins themselves.
Ejecting
If you’d like to customize them you can fully customize them by “ejecting” the code and making it your own.
While eventually we’ll have a CLI that will eject skins in your preferred framework and style, for now we invite you to try it out with these copy-paste-ready implementations.
Default Video Skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode, isValidElement } from 'react';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, QualityIcon, RestartIcon, SeekIcon, SpeechIcon, SpeedIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { createPlayer, Poster, Container, usePlayer, AirPlayButton, useAudioTrackOptions, BufferingIndicator, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, Popover, useQualityOptions, SeekButton, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider, type RenderProp } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './player.css';
const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;
const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;
function MenuChevron({ flipped = false }: { flipped?: boolean }): ReactNode {
return <ChevronIcon className={`media-icon media-menu__chevron ${flipped ? 'media-icon--flipped' : undefined}`} />;
}
function SettingsMenu(): ReactNode {
const playbackRate = usePlaybackRateOptions();
const quality = useQualityOptions();
const audioTrack = useAudioTrackOptions();
const captions = useCaptionsOptions();
const hasPlaybackRate = playbackRate?.state.availability === 'available';
const hasQuality = quality?.state.availability === 'available';
const hasAudioTrack = audioTrack?.state.availability === 'available';
const hasCaptions = captions?.state.availability === 'available';
if (!hasPlaybackRate && !hasQuality && !hasAudioTrack && !hasCaptions) return null;
return (
<Menu.Root side="top" align="center">
<Menu.Trigger aria-label="Settings" className="media-button--settings" render={<Button />}>
<GearIcon className="media-icon media-icon--settings" />
</Menu.Trigger>
<Menu.Content className="media-surface media-popover media-menu media-menu--settings">
<Menu.View className="media-menu__panel">
<div className="media-menu__group">
{hasQuality ? (
<Menu.Root>
<Menu.Trigger
type="quality"
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<QualityIcon className="media-icon" />
<span>Quality</span>
<span className="media-menu__hint">
<Menu.ItemValue className="media-menu__hint-label" />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Back className="media-menu__back">
<MenuChevron flipped />
Quality
</Menu.Back>
<Menu.Separator className="media-menu__separator" />
<Menu.RadioGroup
className="media-menu__group"
value={quality.value}
onValueChange={quality.setValue}
aria-label="Quality"
>
{quality.options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<span>
{option.label}
{option.tier ? <sup className="media-menu__tier">{option.tier}</sup> : null}
</span>
{option.badge ? <span className="media-badge">{option.badge}</span> : null}
<Menu.ItemIndicator
checked={option.value === quality.value}
forceMount
className="media-menu__indicator"
>
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasAudioTrack ? (
<Menu.Root>
<Menu.Trigger
type="audio-track"
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<SpeechIcon className="media-icon" />
<span>Audio</span>
<span className="media-menu__hint">
<Menu.ItemValue className="media-menu__hint-label" />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Back className="media-menu__back">
<MenuChevron flipped />
Audio
</Menu.Back>
<Menu.Separator className="media-menu__separator" />
<Menu.RadioGroup
className="media-menu__group"
value={audioTrack.value}
onValueChange={audioTrack.setValue}
aria-label="Audio tracks"
>
{audioTrack.options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === audioTrack.value}
forceMount
className="media-menu__indicator"
>
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasPlaybackRate ? (
<Menu.Root>
<Menu.Trigger
type="playback-rate"
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<SpeedIcon className="media-icon" />
<span>Speed</span>
<span className="media-menu__hint">
<Menu.ItemValue className="media-menu__hint-label" />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Back className="media-menu__back">
<MenuChevron flipped />
Speed
</Menu.Back>
<Menu.Separator className="media-menu__separator" />
<Menu.RadioGroup
className="media-menu__group"
value={playbackRate.value}
onValueChange={playbackRate.setValue}
aria-label="Playback rate"
>
{playbackRate.options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === playbackRate.value}
forceMount
className="media-menu__indicator"
>
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasCaptions ? (
<Menu.Root>
<Menu.Trigger
type="captions"
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<CaptionsOffIcon className="media-icon" />
<span>Captions</span>
<span className="media-menu__hint">
<Menu.ItemValue className="media-menu__hint-label" />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Back className="media-menu__back">
<MenuChevron flipped />
Captions
</Menu.Back>
<Menu.Separator className="media-menu__separator" />
<Menu.RadioGroup
className="media-menu__group"
value={captions.value}
onValueChange={captions.setValue}
aria-label="Captions"
>
{captions.options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === captions.value}
forceMount
className="media-menu__indicator"
>
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
</div>
</Menu.View>
</Menu.Content>
</Menu.Root>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
export const Player = createPlayer({ features: videoFeatures });
export interface VideoPlayerProps {
src: string;
style?: CSSProperties;
className?: string;
poster?: string | RenderProp<Poster.State> | undefined;
}
/**
* @example
* ```tsx
* <VideoPlayer
* src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
* poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp"
* />
* ```
*/
export function VideoPlayer({ src, className, poster, placeholder, style, ...rest }: VideoPlayerProps): ReactNode {
const containerStyle = placeholder
? ({ '--media-poster-placeholder': `url(${placeholder})`, ...style } as CSSProperties)
: style;
return (
<Player.Provider>
<Container
className={`media-default-skin media-default-skin--video ${className ?? ''}`}
style={containerStyle}
{...rest}
>
<Video src={src} playsInline />
{poster && (
<Poster src={isString(poster) ? poster : undefined} render={isRenderProp(poster) ? poster : undefined} />
)}
<BufferingIndicator
render={(props) => (
<div {...props} className="media-buffering-indicator">
<SpinnerIcon className="media-icon" />
</div>
)}
/>
<ErrorDialog.Root>
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog media-surface">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title">Something went wrong.</ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--primary">OK</ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root className="media-surface media-controls">
<Tooltip.Provider>
<div className="media-button-group">
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PlayButton className="media-button--play" render={<Button />}>
<RestartIcon className="media-icon media-icon--restart" />
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</PlayButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek media-icon--flipped" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
</div>
<div className="media-time-controls">
<Time.Value type="current" className="media-time" />
<TimeSlider.Root className="media-slider">
<TimeSlider.Track className="media-slider__track">
<TimeSlider.Fill className="media-slider__fill" />
<TimeSlider.Buffer className="media-slider__buffer" />
</TimeSlider.Track>
<TimeSlider.Thumb className="media-slider__thumb" />
<div className="media-surface media-thumbnail media-slider__thumbnail">
<Slider.Thumbnail className="media-thumbnail__image" />
<TimeSlider.Value type="pointer" className="media-time media-thumbnail__time" />
<SpinnerIcon className="media-thumbnail__spinner media-icon" />
</div>
<TimeSlider.Preview className="media-slider__preview">
<TimeSlider.Value type="pointer" className="media-time media-slider__value" />
</TimeSlider.Preview>
</TimeSlider.Root>
<Time.Value toggle type="remaining" className="media-time" />
</div>
<div className="media-button-group">
<VolumePopover />
<SettingsMenu />
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CastButton className="media-button--cast" render={<Button />}>
<CastEnterIcon className="media-icon media-icon--cast-enter" />
<CastExitIcon className="media-icon media-icon--cast-exit" />
</CastButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<AirPlayButton className="media-button--airplay" render={<Button />}>
<AirPlayEnterIcon className="media-icon media-icon--airplay-enter" />
<AirPlayExitIcon className="media-icon media-icon--airplay-exit" />
</AirPlayButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PiPButton className="media-button--pip" render={<Button />}>
<PipEnterIcon className="media-icon media-icon--pip-enter" />
<PipExitIcon className="media-icon media-icon--pip-exit" />
</PiPButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<FullscreenButton className="media-button--fullscreen" render={<Button />}>
<FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" />
<FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" />
</FullscreenButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
</div>
</Tooltip.Provider>
</Controls.Root>
<div className="media-overlay" />
{/* Hotkeys */}
<Hotkey keys="Space" action="togglePaused" />
<Hotkey keys="k" action="togglePaused" />
<Hotkey keys="m" action="toggleMuted" />
<Hotkey keys="f" action="toggleFullscreen" />
<Hotkey keys="c" action="toggleSubtitles" />
<Hotkey keys="i" action="togglePictureInPicture" />
<Hotkey keys="ArrowRight" action="seekStep" value={SEEK_TIME / 2} />
<Hotkey keys="ArrowLeft" action="seekStep" value={-(SEEK_TIME / 2)} />
<Hotkey keys="l" action="seekStep" value={SEEK_TIME} />
<Hotkey keys="j" action="seekStep" value={-SEEK_TIME} />
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
<Hotkey keys="0-9" action="seekToPercent" />
<Hotkey keys="Home" action="seekToPercent" value={0} />
<Hotkey keys="End" action="seekToPercent" value={100} />
<Hotkey keys=">" action="speedUp" />
<Hotkey keys="<" action="speedDown" />
{/* Gestures */}
<Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
<Gesture type="tap" action="toggleControls" pointer="touch" />
<Gesture type="doubletap" action="seekStep" value={-SEEK_TIME} region="left" />
<Gesture type="doubletap" action="toggleFullscreen" region="center" />
<Gesture type="doubletap" action="seekStep" value={SEEK_TIME} region="right" />
{/* Input Feedback */}
<StatusAnnouncer />
<div className="media-input-feedback">
<VolumeIndicator.Root className="media-surface media-input-feedback-island media-input-feedback-island--volume">
<VolumeIndicator.Fill className="media-input-feedback-island__content">
<VolumeHighIcon className="media-icon media-icon--volume-high" />
<VolumeLowIcon className="media-icon media-icon--volume-low" />
<VolumeOffIcon className="media-icon media-icon--volume-off" />
<VolumeIndicator.Value className="media-input-feedback-island__value" />
</VolumeIndicator.Fill>
</VolumeIndicator.Root>
<StatusIndicator.Root
actions={TOP_STATUS_ACTIONS}
className="media-surface media-input-feedback-island media-input-feedback-island--status"
>
<div className="media-input-feedback-island__content">
<CaptionsOnIcon className="media-icon media-icon--captions-on" />
<CaptionsOffIcon className="media-icon media-icon--captions-off" />
<FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" />
<FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" />
<PipEnterIcon className="media-icon media-icon--pip-enter" />
<PipExitIcon className="media-icon media-icon--pip-exit" />
<StatusIndicator.Value className="media-input-feedback-island__value" />
</div>
</StatusIndicator.Root>
<SeekIndicator.Root className="media-input-feedback-bubble">
<ChevronIcon className="media-icon media-icon--seek" />
<SeekIndicator.Value className="media-time" />
</SeekIndicator.Root>
<StatusIndicator.Root actions={CENTER_STATUS_ACTIONS} className="media-input-feedback-bubble">
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</StatusIndicator.Root>
</div>
</Container>
</Player.Provider>
);
}
// ================================================================
// Components
// ================================================================
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
return (
<button
ref={ref}
type="button"
className={`media-button media-button--subtle media-button--icon ${className ?? ''}`}
{...props}
/>
);
});
function VolumePopover(): ReactNode {
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
const muteButton = (
<MuteButton className="media-button--mute" render={<Button />}>
<VolumeOffIcon className="media-icon media-icon--volume-off" />
<VolumeLowIcon className="media-icon media-icon--volume-low" />
<VolumeHighIcon className="media-icon media-icon--volume-high" />
</MuteButton>
);
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top">
<Popover.Trigger render={muteButton} />
<Popover.Popup className="media-surface media-popover media-popover--volume">
<VolumeSlider.Root className="media-slider" orientation="vertical" thumbAlignment="edge">
<VolumeSlider.Track className="media-slider__track">
<VolumeSlider.Fill className="media-slider__fill" />
</VolumeSlider.Track>
<VolumeSlider.Thumb className="media-slider__thumb media-slider__thumb--persistent" />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Root>
);
}
// ================================================================
// Utilities
// ================================================================
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isRenderProp(value: unknown): value is RenderProp<unknown> {
return typeof value === 'function' || isValidElement(value);
}
/* ==========================================================================
Reset
========================================================================== */
.media-default-skin *,
.media-default-skin *::before,
.media-default-skin *::after {
box-sizing: border-box;
}
.media-default-skin img,
.media-default-skin video,
.media-default-skin svg {
display: block;
max-width: 100%;
}
.media-default-skin button {
font: inherit;
}
.media-default-skin [hidden][hidden] {
/* Keep authored templates hidden even when component classes set display. */
display: none;
}
@media (prefers-reduced-motion: no-preference) {
.media-default-skin {
interpolate-size: allow-keywords;
}
}
/* ==========================================================================
Root Container
========================================================================== */
.media-default-skin {
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
--media-icon-size: 18px;
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
position: relative;
display: block;
width: 100%;
height: 100%;
container: media-root / inline-size;
font-family:
Inter Variable,
Inter,
ui-sans-serif,
system-ui,
sans-serif;
font-size: 0.8125rem; /* 13px at 100% font size */
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
scrollbar-width: thin;
border-radius: var(--media-border-radius, 2rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-color-scrollbar-thumb);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
/* ==========================================================================
Surface (shared glass effect for tooltips, popovers, controls)
========================================================================== */
.media-default-skin .media-surface {
background-color: var(--media-surface-background-color);
box-shadow:
0 0 0 1px var(--media-surface-outer-border-color),
0 1px 3px 0 var(--media-surface-shadow-color),
0 1px 2px -1px var(--media-surface-shadow-color);
backdrop-filter: var(--media-surface-backdrop-filter);
/* Inner border ring */
&::after {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
content: "";
border-radius: inherit;
box-shadow: inset 0 0 0 1px var(--media-surface-inner-border-color);
}
}
/* ==========================================================================
Media Element
========================================================================== */
.media-default-skin ::slotted(video),
.media-default-skin video {
display: block;
width: 100%;
height: 100%;
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
}
.media-default-skin ::slotted(video) {
border-radius: var(--media-video-border-radius);
}
.media-default-skin video {
border-radius: inherit;
}
.media-default-skin:fullscreen ::slotted(video),
.media-default-skin:fullscreen video {
object-fit: contain;
}
/* ==========================================================================
Overlay / Scrim
========================================================================== */
.media-default-skin .media-overlay {
position: absolute;
inset: 0;
pointer-events: none;
background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.3) 25%, oklch(0 0 0 / 0));
border-radius: inherit;
opacity: 0;
backdrop-filter: blur(0) saturate(1);
transition-timing-function: ease-out;
transition-duration: var(--media-controls-transition-duration);
transition-property: opacity, backdrop-filter;
}
.media-default-skin .media-error ~ .media-overlay {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-default-skin .media-controls[data-visible] ~ .media-overlay,
.media-default-skin .media-error[data-open] ~ .media-overlay {
opacity: 1;
}
.media-default-skin .media-buffering-indicator[data-visible] ~ .media-overlay {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-default-skin .media-error[data-open] ~ .media-overlay {
backdrop-filter: blur(16px) saturate(1.5);
}
/* ==========================================================================
Buffering Indicator
========================================================================== */
.media-default-skin .media-buffering-indicator {
position: absolute;
inset: 0;
z-index: 10;
display: none;
place-content: center;
color: oklch(1 0 0);
pointer-events: none;
&:not([data-visible]) {
--media-spinner-animation: none;
}
&[data-visible] {
display: grid;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-default-skin .media-error {
outline: none;
}
.media-default-skin .media-error:not([data-open]) {
display: none;
}
.media-default-skin .media-error__title {
font-weight: 600;
line-height: 1.25;
}
.media-default-skin .media-error__description {
overflow-wrap: anywhere;
opacity: 0.7;
}
.media-default-skin .media-error__actions {
display: flex;
gap: 0.5rem;
& > * {
flex: 1;
}
}
.media-default-skin .media-error[data-open] ~ .media-controls * {
visibility: hidden;
}
/* ==========================================================================
Controls
========================================================================== */
.media-default-skin .media-controls {
display: flex;
column-gap: 0.075rem;
align-items: center;
padding: 0.375rem;
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
border-radius: 1.5rem;
}
/* ==========================================================================
Time Display
========================================================================== */
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: 0.75rem;
align-items: center;
padding-inline: 0.5rem;
container: media-time-controls / inline-size;
}
.media-default-skin .media-time {
font-variant-numeric: tabular-nums;
}
.media-default-skin .media-time[role="button"] {
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: 0.25rem;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
/* ==========================================================================
Buttons
========================================================================== */
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
min-height: 0;
padding: 0.5rem 1rem;
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(infinity * 1px);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:active {
scale: 0.98;
}
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: oklch(0 0 0);
text-shadow: none;
background: oklch(1 0 0);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
text-decoration: none;
background-color: oklch(from currentColor l c h / 0.1);
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
width: 2.25rem;
aspect-ratio: 1;
padding: 0;
&:active {
scale: 0.9;
}
& .media-icon__container {
display: grid;
}
& .media-icon {
grid-area: 1 / 1;
transition-behavior: allow-discrete;
transition-property: display, opacity;
transition-duration: 150ms;
transition-timing-function: ease-out;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
/* Seek button */
.media-default-skin .media-button--seek {
& .media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 10px;
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
}
&:has(.media-icon--flipped) .media-icon__label {
right: unset;
left: -1px;
}
}
/* Playback rate button */
.media-default-skin .media-button--playback-rate {
padding: 0;
font-variant-numeric: tabular-nums;
&::after {
width: 4ch;
content: attr(data-rate) "\00D7";
}
&[data-inline-rate-label]::after {
content: none;
}
}
/* Settings button */
.media-default-skin .media-button--settings {
display: none;
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
&[aria-expanded="true"] .media-icon--settings {
transform: rotate(90deg);
}
}
.media-default-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
display: grid;
}
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
text content. */
.media-default-skin .media-button--live {
display: inline-flex;
gap: 0.4rem;
align-items: center;
width: auto;
aspect-ratio: auto;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
transition: background-color 150ms ease-out;
}
&[data-live-edge]::before {
background-color: oklch(0.65 0.22 27);
}
}
/* ==========================================================================
Button Groups
========================================================================== */
.media-default-skin .media-button-group {
display: flex;
gap: 0.075rem;
align-items: center;
@container media-root (width > 42rem) {
gap: 0.125rem;
}
}
/* ==========================================================================
Badge
========================================================================== */
.media-default-skin .media-badge {
padding: 0.125rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
border-radius: calc(infinity * 1px);
}
/* ==========================================================================
Icons
========================================================================== */
.media-default-skin .media-icon__container {
position: relative;
}
.media-default-skin .media-icon {
flex-shrink: 0;
width: var(--media-icon-size);
height: var(--media-icon-size);
}
.media-default-skin .media-icon--flipped {
scale: -1 1;
}
/* ==========================================================================
Poster Image
========================================================================== */
.media-default-skin media-poster,
.media-default-skin > img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
transition: opacity 0.25s;
}
.media-default-skin media-poster:not([data-visible]),
.media-default-skin > img:not([data-visible]),
.media-default-skin > img[data-visible]:not([data-loaded]) {
opacity: 0;
}
.media-default-skin media-poster ::slotted(img),
.media-default-skin media-poster img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: var(--media-video-border-radius);
}
.media-default-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
/* Blurred placeholder: HTML path targets media-poster::before, React path targets the skin container::before */
.media-default-skin media-poster::before,
.media-default-skin::before {
position: absolute;
inset: 0;
pointer-events: none;
content: "";
background-image: var(--media-poster-placeholder, none);
background-repeat: no-repeat;
background-position: var(--media-object-position, center);
background-size: var(--media-object-fit, contain);
filter: blur(var(--media-poster-placeholder-blur, 20px));
}
/* React path: hidden by default, transitions in while poster loads */
.media-default-skin::before {
opacity: 0;
transition: opacity 0.25s;
}
/* Show placeholder while the poster is visible but not yet loaded */
.media-default-skin:has(img[data-visible]:not([data-loaded]))::before {
opacity: 1;
}
.media-default-skin:fullscreen media-poster ::slotted(img),
.media-default-skin:fullscreen media-poster img,
.media-default-skin:fullscreen > img {
object-fit: contain;
}
/* ==========================================================================
Media thumbnail
========================================================================== */
.media-default-skin .media-thumbnail {
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: 0.75rem;
& .media-thumbnail__image {
position: relative;
display: block;
overflow: clip;
border-radius: inherit;
&::after {
position: absolute;
inset: 0;
content: "";
background-image: linear-gradient(to top, oklch(0 0 0 / 0.8), oklch(0 0 0 / 0.3), oklch(0 0 0 / 0));
border-radius: inherit;
}
}
& .media-thumbnail__time {
position: absolute;
inset-inline: 0;
bottom: 0.5rem;
text-align: center;
}
& .media-thumbnail__spinner {
position: absolute;
top: 50%;
left: 50%;
opacity: 0;
translate: -50% -50%;
}
& .media-thumbnail__image,
& .media-thumbnail__spinner {
transition: opacity 150ms ease-out;
}
&:not(:has(.media-thumbnail__image[data-loading])) {
& .media-thumbnail__spinner {
--media-spinner-animation: none;
}
}
&:has(.media-thumbnail__image[data-loading]) {
& .media-thumbnail__image {
opacity: 0;
}
& .media-thumbnail__spinner {
opacity: 1;
}
}
}
/* ==========================================================================
Slider
========================================================================== */
.media-default-skin .media-slider {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: calc(infinity * 1px);
&[data-orientation="horizontal"] {
width: 100%;
min-width: 5rem;
height: 2rem;
}
&[data-orientation="vertical"] {
width: 2rem;
height: 5rem;
}
}
/* Track */
.media-default-skin .media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
border-radius: inherit;
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: 0.25rem;
}
&[data-orientation="vertical"] {
width: 0.25rem;
height: 100%;
}
}
/* Thumb */
.media-default-skin .media-slider__thumb {
position: absolute;
z-index: 10;
width: 0.625rem;
height: 0.625rem;
user-select: none;
outline: 4px solid transparent;
outline-offset: -4px;
background-color: currentColor;
border-radius: calc(infinity * 1px);
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.1)),
0 1px 3px 0 oklch(0 0 0 / 0.35),
0 1px 2px -1px oklch(0 0 0 / 0.35);
opacity: 0;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, height, width, outline-offset;
&[data-orientation="horizontal"] {
top: 50%;
left: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-fill));
left: 50%;
}
&:hover,
&:focus {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
}
.media-default-skin .media-slider:active .media-slider__thumb,
.media-default-skin .media-slider__thumb--persistent {
width: 0.75rem;
height: 0.75rem;
}
.media-default-skin .media-slider:hover .media-slider__thumb,
.media-default-skin .media-slider__thumb:focus-visible,
.media-default-skin .media-slider__thumb--persistent {
opacity: 1;
}
/* Preview */
.media-default-skin .media-slider__preview {
& .media-slider__value,
&::before {
opacity: 0;
scale: 0.5;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
& .media-slider__value {
position: absolute;
bottom: 2.25rem;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
filter: blur(8px);
translate: -50% 0.5rem;
transition-property: filter, opacity, scale, translate;
}
&::before {
display: block;
min-width: 0.25rem;
height: 0.25rem;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
}
&[data-pointing] .media-slider__value,
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
&[data-pointing] .media-slider__value {
filter: blur(0);
translate: -50% 0;
}
}
/* Shared track fills */
.media-default-skin .media-slider__buffer,
.media-default-skin .media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
}
.media-default-skin .media-slider__buffer[data-orientation="horizontal"],
.media-default-skin .media-slider__fill[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
}
.media-default-skin .media-slider__buffer[data-orientation="vertical"],
.media-default-skin .media-slider__fill[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
}
/* Buffer */
.media-default-skin .media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
transition-timing-function: ease-out;
transition-duration: 0.25s;
&[data-orientation="horizontal"] {
width: var(--media-slider-buffer);
transition-property: width;
}
&[data-orientation="vertical"] {
height: var(--media-slider-buffer);
transition-property: height;
}
}
/* Fill */
.media-default-skin .media-slider__fill {
background-color: currentColor;
&[data-orientation="horizontal"] {
width: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
height: var(--media-slider-fill);
}
}
/* Dragging — thumb and fill follow the pointer position */
.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
width: var(--media-slider-pointer);
}
.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
height: var(--media-slider-pointer);
}
/* ==========================================================================
Popups & Tooltips
========================================================================== */
.media-default-skin {
--media-popup-transition:
opacity var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
filter var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
transform var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
scale var(--media-popup-transition-timing-function) var(--media-popup-transition-duration);
}
.media-default-skin .media-popover,
.media-default-skin .media-tooltip {
--popup-translate-distance: 0.5rem;
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
filter: blur(4px);
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--popup-translate-x-distance, 0), var(--popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--popup-translate-y-distance: var(--popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--popup-translate-y-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--popup-translate-x-distance: var(--popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--popup-translate-x-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: left;
}
/* Safe area between trigger and popup */
&::before {
position: absolute;
pointer-events: inherit;
content: "";
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
inset-inline: 0;
width: 100%;
}
&[data-side="top"]::before {
top: 100%;
}
&[data-side="bottom"]::before {
bottom: 100%;
}
&[data-side="left"]::before,
&[data-side="right"]::before {
inset-block: 0;
height: 100%;
}
&[data-side="left"]::before {
left: 100%;
}
&[data-side="right"]::before {
right: 100%;
}
}
.media-default-skin .media-popover {
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-popover-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-popover-side-offset);
}
}
.media-default-skin .media-popover--volume {
padding: 0.75rem 0;
border-radius: calc(infinity * 1px);
&:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
white-space: nowrap;
border-radius: calc(infinity * 1px);
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
column-gap: 0.25rem;
align-items: center;
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-tooltip-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-tooltip-side-offset);
}
& .media-tooltip__kbd {
min-width: 1.5em;
padding: 0.1em;
font-family: inherit;
font-size: 90%;
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: 0.25rem;
}
}
/* ==========================================================================
Menus
Note: Menus use `.media-popover` styles for positioning and transitions.
========================================================================== */
.media-default-skin .media-menu {
--menu-transition-duration: 250ms;
--menu-max-height: 14rem;
--menu-padding: 0.25rem;
--menu-border-radius: 0.75rem;
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--menu-transition-duration: 0ms;
}
& .media-menu__panel {
position: absolute;
inset: 0;
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-in-out;
transition-duration: var(--menu-transition-duration);
transition-property: translate, filter;
will-change: translate, filter;
&[data-starting-style],
&[data-ending-style] {
overflow: hidden;
}
/* Root settings view — slides out when a submenu is active */
&[data-menu-root-view][data-menu-view-state="inactive"] {
filter: blur(8px);
translate: -100% 0;
/* Hide the indicator while transitioning */
& .media-menu__group::before {
display: none;
}
}
/* Submenu panels — slide in/out alongside the root view */
&[data-submenu] {
z-index: 10;
&:not([data-open], [data-ending-style]) {
translate: -100% 0;
transition-property: none;
}
&[data-starting-style],
&[data-ending-style] {
pointer-events: none;
filter: blur(8px);
}
&[data-starting-style][data-direction="forward"],
&[data-ending-style][data-direction="back"] {
translate: 100% 0;
}
&[data-ending-style][data-direction="forward"],
&[data-starting-style][data-direction="back"] {
translate: -100% 0;
}
}
}
& .media-menu__separator {
margin-block: 0.25rem;
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
& .media-menu__group {
display: flex;
flex-direction: column;
gap: 0.125rem;
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --media-menu-item-highlight-anchor;
inset: anchor(inside);
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.1);
border-radius: var(--menu-item-border-radius);
transition: inset ease-in-out 100ms;
}
}
}
& .media-menu__item,
& .media-menu__back {
display: flex;
gap: 0.375rem;
align-items: center;
padding: 0.375rem 0.5rem;
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-out;
transition-duration: 100ms;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
@supports (top: anchor(top)) {
anchor-name: --media-menu-item-highlight-anchor;
background-color: transparent;
}
}
& .media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__indicator {
flex-shrink: 0;
margin-right: -0.25rem;
margin-left: auto;
opacity: 0;
& .media-icon {
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__item {
justify-content: space-between;
font-variant-numeric: tabular-nums;
color: inherit;
&[aria-disabled="true"] {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
&[aria-checked="true"] .media-menu__indicator {
opacity: 1;
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
& .media-menu__tier {
padding-top: 1px;
padding-left: 0.125rem;
font-size: 0.5625rem;
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
& .media-menu__back {
width: 100%;
margin-bottom: 0.125rem;
}
& .media-menu__hint {
display: inline-flex;
gap: 0.25rem;
align-items: center;
min-width: 0;
padding-left: 0.5rem;
margin-left: auto;
font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
max-width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
width: 0.875rem;
height: 0.875rem;
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: 12rem;
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
transition:
var(--media-popup-transition),
height var(--media-popup-transition-timing-function) var(--menu-transition-duration),
width var(--media-popup-transition-timing-function) var(--menu-transition-duration);
/* Don't transition width and height on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
/* ==========================================================================
Native Caption Track
========================================================================== */
.media-default-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: -0.5rem;
&:has(.media-controls[data-visible]) {
--media-caption-track-y: -5.5rem;
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
--media-caption-track-y: -3.5rem;
}
}
}
.media-default-skin video::-webkit-media-text-track-container {
z-index: 1;
font-family: inherit;
scale: 0.98;
translate: 0 var(--media-caption-track-y);
transition: translate var(--media-caption-track-duration) ease-out;
transition-delay: var(--media-caption-track-delay);
}
/* ==========================================================================
Input Feedback
========================================================================== */
.media-default-skin .media-input-feedback {
position: absolute;
inset-inline: 0;
top: 0;
bottom: 3.5rem; /* Shift up a little in smaller containers */
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
color: var(--media-color-primary, oklch(1 0 0));
pointer-events: none;
@container media-root (width > 24rem) {
bottom: 0;
}
}
/* --- Feedback islands ------------------------------------------------------- */
.media-default-skin .media-input-feedback-island {
--media-surface-background-color: oklch(0 0 0 / 0.25);
position: absolute;
top: 0.75rem;
font-weight: 500;
color: inherit;
pointer-events: none;
border-radius: calc(Infinity * 1px);
transform-origin: top center;
transition-timing-function: ease-out;
transition-duration: 100ms;
.media-input-feedback-island__content {
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0.25rem 0.625rem;
/* Increase contrast of the content */
* {
mix-blend-mode: difference;
}
}
.media-icon {
display: none;
flex-shrink: 0;
}
.media-input-feedback-island__value {
margin-left: auto;
}
@media (pointer: coarse) {
transition-property: scale, translate, opacity;
will-change: scale, translate, opacity;
}
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
transition-property: scale, translate, filter, opacity;
will-change: scale, translate, filter, opacity;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: oklch(0 0 0);
}
/* Default hidden state */
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
transition-timing-function: ease-in;
transition-duration: 250ms;
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
scale: 0.9;
}
@media (prefers-reduced-motion: no-preference) {
&[data-ending-style] {
translate: 0 -25%;
}
}
}
}
.media-default-skin .media-input-feedback-island--volume {
width: min(80%, 12rem);
.media-input-feedback-island__content {
--media-progress-fill: var(--media-volume-fill);
background-image: linear-gradient(
to right,
currentColor 0%,
currentColor var(--media-progress-fill),
transparent var(--media-progress-fill),
transparent 100%
);
border-radius: inherit;
transition: --media-progress-fill 200ms linear;
}
&[data-level="high"] .media-icon--volume-high,
&[data-level="low"] .media-icon--volume-low,
&[data-level="off"] .media-icon--volume-off {
display: block;
}
@media (prefers-reduced-motion: no-preference) {
&[data-min],
&[data-max] {
animation: media-shake 300ms ease-in-out;
}
}
}
.media-default-skin .media-input-feedback-island--status {
&[data-status="captions-on"] .media-icon--captions-on,
&[data-status="captions-off"] .media-icon--captions-off,
&[data-status="fullscreen"] .media-icon--fullscreen-enter,
&[data-status="exit-fullscreen"] .media-icon--fullscreen-exit,
&[data-status="pip"] .media-icon--pip-enter,
&[data-status="exit-pip"] .media-icon--pip-exit {
display: block;
}
}
/* --- Bubble ---------------------------------------------------------------- */
.media-default-skin .media-input-feedback-bubble {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: 1rem;
text-align: center;
/* Central bubble (play, pause) */
&:not([data-direction]) {
background: oklch(0 0 0 / 0.35);
border-radius: 100%;
backdrop-filter: blur(8px);
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
scale: 0.85;
}
&[data-ending-style] {
transition-timing-function: ease-in;
transition-duration: 100ms;
}
@media (prefers-reduced-motion: reduce) {
transition-duration: 50ms;
transition-property: opacity;
}
}
/* Directional bubbles (seek) */
&[data-direction] {
gap: 0.25rem;
@container media-root (width > 24rem) {
padding: 1.5rem;
}
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
/* Icons */
.media-icon {
display: none;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
&.media-icon--play {
translate: 1px 0;
}
}
&[data-direction] .media-icon--seek,
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
display: block;
}
&[data-direction="backward"] .media-icon--seek {
scale: -1 1;
}
@media (prefers-reduced-motion: no-preference) {
.media-icon--seek {
transition-timing-function: ease-in-out;
transition-duration: 200ms;
transition-property: translate, opacity;
}
&[data-starting-style] .media-icon--seek,
&[data-ending-style] .media-icon--seek {
opacity: 0;
}
&[data-direction="forward"][data-starting-style] .media-icon--seek {
translate: -60% 0;
}
&[data-direction="backward"][data-starting-style] .media-icon--seek {
translate: 60% 0;
}
}
}
/* ==========================================================================
Icon State Visibility for Video Skins
Data-attribute-driven visibility rules for multi-state icon buttons.
Uses :is() with both element selectors (for HTML custom element wrappers)
and class selectors (for React rendered SVG elements).
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon--restart,
.media-button--play .media-icon--play,
.media-button--play .media-icon--pause,
.media-button--mute .media-icon--volume-off,
.media-button--mute .media-icon--volume-low,
.media-button--mute .media-icon--volume-high,
.media-button--fullscreen .media-icon--fullscreen-enter,
.media-button--fullscreen .media-icon--fullscreen-exit,
.media-button--pip .media-icon--pip-enter,
.media-button--pip .media-icon--pip-exit,
.media-button--cast .media-icon--cast-enter,
.media-button--cast .media-icon--cast-exit,
.media-button--airplay .media-icon--airplay-enter,
.media-button--airplay .media-icon--airplay-exit,
.media-button--captions .media-icon--captions-off,
.media-button--captions .media-icon--captions-on {
display: none;
opacity: 0;
}
/* --- Active icon per state --- */
/* Play: ended → restart */
.media-button--play[data-ended] .media-icon--restart,
/* Play: paused or not yet started (not ended) → play */
.media-button--play:not([data-ended])[data-paused] .media-icon--play,
.media-button--play:not([data-ended]):not([data-started]) .media-icon--play,
/* Play: started and not paused/ended → pause */
.media-button--play[data-started]:not([data-paused]):not([data-ended]) .media-icon--pause,
/* Mute: muted → volume off */
.media-button--mute[data-muted] .media-icon--volume-off,
/* Mute: volume low (not muted) → volume low */
.media-button--mute:not([data-muted])[data-volume-level="low"] .media-icon--volume-low,
/* Mute: volume high (not muted, not low) → volume high */
.media-button--mute:not([data-muted]):not([data-volume-level="low"]) .media-icon--volume-high,
/* Fullscreen: not fullscreen → enter */
.media-button--fullscreen:not([data-fullscreen]) .media-icon--fullscreen-enter,
/* Fullscreen: fullscreen → exit */
.media-button--fullscreen[data-fullscreen] .media-icon--fullscreen-exit,
/* Picture-in-Picture: not active → enter */
.media-button--pip:not([data-pip]) .media-icon--pip-enter,
/* Picture-in-Picture: active → exit */
.media-button--pip[data-pip] .media-icon--pip-exit,
/* Cast: not connected → enter */
.media-button--cast:not([data-cast-state="connected"]) .media-icon--cast-enter,
/* Cast: connected → exit */
.media-button--cast[data-cast-state="connected"] .media-icon--cast-exit,
/* AirPlay: not connected → enter */
.media-button--airplay:not([data-airplay-state="connected"]) .media-icon--airplay-enter,
/* AirPlay: connected → exit */
.media-button--airplay[data-airplay-state="connected"] .media-icon--airplay-exit,
/* Captions: not active → captions off */
.media-button--captions:not([data-active]) .media-icon--captions-off,
/* Captions: active → captions on */
.media-button--captions[data-active] .media-icon--captions-on {
display: block;
opacity: 1;
}
/* --- Pause keyframe animations on inactive icons --- */
/* The airplay-exit SVG defines its keyframes against CSS variables (mirroring
the spinner pattern). When the AirPlay session isn't active the SVG is
still in the DOM — just `display: none` — so its animations would keep
running. Set the variables to `none` to short-circuit the keyframes. */
.media-button--airplay:not([data-airplay-state="connected"]) {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
/* -------------------------------------------------------------------------- */
/* Global @keyframes for all video skins (CSS & Tailwind) */
/* -------------------------------------------------------------------------- */
@keyframes media-shake {
0%,
100% {
translate: 0 0;
}
20% {
translate: -6px 0;
}
40% {
translate: 4px 0;
}
60% {
translate: -2px 0;
}
80% {
translate: 1px 0;
}
}
/* -------------------------------------------------------------------------- */
/* Global @properties for all video skins (CSS & Tailwind) */
/* -------------------------------------------------------------------------- */
@property --media-progress-fill {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
/* ==========================================================================
Root
========================================================================== */
.media-default-skin--video {
--media-spring-timing-function: linear(
0,
0.034 1.5%,
0.763 9.7%,
1.066 13.9%,
1.198 19.9%,
1.184 21.8%,
0.963 37.5%,
0.997 50.9%,
1
);
--media-border-color: oklch(0 0 0 / 0.1);
--media-surface-background-color: oklch(1 0 0 / 0.1);
--media-surface-inner-border-color: oklch(1 0 0 / 0.05);
--media-surface-outer-border-color: oklch(0 0 0 / 0.1);
--media-surface-shadow-color: oklch(0 0 0 / 0.15);
--media-surface-backdrop-filter: blur(16px) saturate(1.5);
--media-video-border-radius: var(--media-border-radius, 2rem);
--media-controls-transition-duration: 100ms;
--media-controls-transition-timing-function: ease-out;
--media-error-dialog-transition-duration: 350ms;
--media-error-dialog-transition-delay: 100ms;
--media-error-dialog-transition-timing-function: var(--media-spring-timing-function);
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
--media-tooltip-side-offset: 0.75rem;
--media-tooltip-boundary-offset: 0.5rem;
--media-popover-side-offset: 0.5rem;
--media-popover-boundary-offset: 0.5rem;
overflow: clip;
background: oklch(0 0 0);
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-error-dialog-transition-timing-function: ease-out;
--media-popup-transition-duration: 0ms;
}
@media (prefers-color-scheme: dark) {
--media-border-color: oklch(1 0 0 / 0.15);
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: oklch(0 0 0);
--media-surface-inner-border-color: oklch(1 0 0 / 0.25);
--media-surface-outer-border-color: transparent;
}
&:has(.media-controls:not([data-visible])) {
/* Slight delay to hide controls on non-touch devices after interaction */
@media (pointer: fine) {
--media-controls-transition-duration: 300ms;
}
@media (pointer: coarse) {
--media-controls-transition-duration: 150ms;
}
@media (prefers-reduced-motion: reduce) {
--media-controls-transition-duration: 50ms;
}
}
/* Inner border ring */
&::after {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
content: "";
border-radius: inherit;
box-shadow: inset 0 0 0 1px var(--media-border-color);
}
&:fullscreen {
--media-border-radius: 0;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-default-skin--video .media-error {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
}
.media-default-skin--video .media-error__dialog {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 18rem;
padding: 0.75rem;
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.25);
border-radius: 1.75rem;
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: var(--media-error-dialog-transition-timing-function);
transition-duration: var(--media-error-dialog-transition-duration);
transition-property: opacity, scale;
}
.media-default-skin--video .media-error[data-starting-style] .media-error__dialog,
.media-default-skin--video .media-error[data-ending-style] .media-error__dialog {
opacity: 0;
scale: 0.5;
}
.media-default-skin--video .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
.media-default-skin--video .media-error__content {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem 0.5rem 0.375rem;
text-shadow: inherit;
}
.media-default-skin--video .media-error__title {
font-size: 1rem;
}
/* ==========================================================================
Controls (hide/show behavior)
========================================================================== */
.media-default-skin--video .media-controls {
position: absolute;
inset-inline: 0.5rem;
bottom: 0.5rem;
z-index: 10;
flex-wrap: wrap;
color: var(--media-color-primary, oklch(1 0 0));
transform-origin: bottom;
transition-timing-function: var(--media-controls-transition-timing-function);
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) {
transition-property: scale, filter, opacity;
will-change: scale, filter, opacity;
}
@media (pointer: coarse) {
transition-property: scale, opacity;
will-change: scale, opacity;
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
scale: 0.95;
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
}
& .media-time-controls {
flex: 0 0 100%;
order: -1;
padding-inline: 0.625rem;
}
& .media-button-group:first-child {
flex: 1;
text-align: left;
}
& .media-button-group:last-child {
flex: 1;
justify-content: end;
}
@container media-root (width > 42rem) {
inset-inline: 0.75rem;
bottom: 0.75rem;
flex-wrap: nowrap;
column-gap: 0.125rem;
padding: 0.25rem;
& .media-time-controls {
flex: 1;
order: unset;
}
& .media-button-group:first-child,
& .media-button-group:last-child {
flex: 0 0 auto;
}
}
}
.media-default-skin--video .media-error[data-open] ~ .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-default-skin--video:has(.media-controls:not([data-visible])) {
cursor: none;
}
/* ==========================================================================
Sliders
========================================================================== */
.media-default-skin--video .media-slider__track {
background-color: oklch(1 0 0 / 0.2);
box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);
}
.media-default-skin--video .media-slider__thumbnail {
--media-slider-thumbnail-max-width: 11rem;
--media-slider-thumbnail-max-height: 8rem;
--media-slider-thumbnail-padding: -1.125rem;
/**
Inset is the difference between the container width and the slider (100%) width.
Divided by 2 as we render the time on both sides.
*/
--media-slider-thumbnail-inset: calc((100cqi - 100%) / 2);
position: absolute;
bottom: calc(100% + 1.2rem);
left: clamp(
calc(
var(--media-slider-thumbnail-max-width) /
2 +
var(--media-slider-thumbnail-padding) -
var(--media-slider-thumbnail-inset)
),
var(--media-slider-pointer),
calc(
100% -
var(--media-slider-thumbnail-max-width) /
2 -
var(--media-slider-thumbnail-padding) +
var(--media-slider-thumbnail-inset)
)
);
pointer-events: none;
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: scale, opacity, filter;
& .media-thumbnail__image {
max-width: var(--media-slider-thumbnail-max-width);
max-height: var(--media-slider-thumbnail-max-height);
}
}
.media-default-skin--video .media-slider[data-pointing] .media-slider__thumbnail:has([role="img"]:not([data-hidden])) {
opacity: 1;
filter: blur(0);
scale: 1;
}
.media-default-skin--video
.media-slider__thumbnail:has([role="img"]:not([data-hidden]))
+ .media-slider__preview
.media-slider__value {
display: none;
}
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode, isValidElement } from 'react';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, QualityIcon, RestartIcon, SeekIcon, SpeechIcon, SpeedIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { createPlayer, Poster, Container, usePlayer, AirPlayButton, useAudioTrackOptions, BufferingIndicator, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, Popover, useQualityOptions, SeekButton, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider, type RenderProp } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './player.css';
const badge = "rounded-full bg-current/10 px-1.5 py-0.5 text-[0.6875rem] font-medium leading-none text-current/70";
const bufferingIndicator = {
root: "peer/buffering absolute inset-0 z-10 hidden place-content-center pointer-events-none text-white not-data-visible:[--media-spinner-animation:none] data-visible:grid",
};
const button = {
base: "flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden",
primary: "bg-white text-black font-medium text-shadow-none",
subtle: "bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10",
icon: "grid w-9 aspect-square p-0 active:scale-90",
live: "inline-flex items-center gap-1.5 aspect-auto w-auto px-3 py-2 text-xs font-semibold uppercase tracking-wider leading-none before:inline-block before:size-2 before:shrink-0 before:rounded-full before:bg-current/40 before:transition-colors before:duration-150 before:ease-out before:content-[\"\"] data-[live-edge]:before:bg-red-500",
};
const buttonGroupEnd = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 justify-end @2xl/media-root:flex-none";
const buttonGroupStart = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 @2xl/media-root:flex-none";
const controls = "peer/controls @container/media-controls p-[0.375rem] flex items-center gap-x-[0.075rem] rounded-3xl text-shadow-2xs text-shadow-(color:--media-current-shadow-color) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 absolute bottom-2 inset-x-2 flex-wrap [color:var(--media-color-primary,oklch(1_0_0))] z-10 peer-data-open/error:hidden ease-(--media-controls-transition-timing-function) origin-bottom duration-(--media-controls-transition-duration) pointer-fine:will-change-[scale,filter,opacity] pointer-fine:transition-[scale,filter,opacity] pointer-coarse:will-change-[scale,opacity] pointer-coarse:transition-[scale,opacity] not-data-visible:pointer-events-none not-data-visible:opacity-0 motion-safe:not-data-visible:scale-90 pointer-fine:motion-safe:not-data-visible:blur-sm @2xl/media-root:bottom-3 @2xl/media-root:inset-x-3 @2xl/media-root:flex-nowrap @2xl/media-root:gap-x-0.5 @2xl/media-root:p-1";
const error = {
root: "peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none",
dialog: "flex flex-col gap-3 max-w-72 p-3 rounded-[1.75rem] text-white transition-[opacity,scale,transform] duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) ease-(--media-error-dialog-transition-timing-function) group-data-starting-style/error:opacity-0 group-data-starting-style/error:scale-50 group-data-ending-style/error:opacity-0 group-data-ending-style/error:scale-50 group-data-ending-style/error:delay-0 bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 text-shadow-2xs text-shadow-black/25",
content: "flex flex-col gap-2 px-2 pt-2 pb-1.5 text-shadow-inherit",
title: "font-semibold leading-tight text-base",
description: "opacity-70 wrap-anywhere",
actions: "flex gap-2 *:flex-1",
};
const icon = "block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out";
const iconContainer = "relative grid";
const iconFlipped = "[scale:-1_1]";
const iconState = {
play: {
button: "group",
restart: "hidden opacity-0 group-data-ended:block group-data-ended:opacity-100",
play: "hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100",
pause: "hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100",
},
mute: {
button: "group",
volumeOff: "hidden opacity-0 group-data-muted:block group-data-muted:opacity-100",
volumeLow: "hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100",
volumeHigh: "hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100",
},
fullscreen: {
button: "group",
enter: "hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100",
exit: "hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100",
},
captions: {
button: "group",
off: "hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100",
on: "hidden opacity-0 group-data-active:block group-data-active:opacity-100",
},
pip: {
button: "group",
off: "hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100",
on: "hidden opacity-0 group-data-pip:block group-data-pip:opacity-100",
},
cast: {
button: "group",
enter: "hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100",
exit: "hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100",
},
airplay: {
button: "group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]",
enter: "hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100",
exit: "hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100",
},
};
const inputFeedback = {
root: "absolute inset-x-0 top-0 bottom-14 pointer-events-none grid grid-cols-3 items-center justify-items-center @2xl/media-root:bottom-0 [color:var(--media-color-primary,oklch(1_0_0))]",
island: {
base: "group/input-indicator [--media-surface-background-color:oklch(0_0_0/0.25)] absolute top-3 rounded-full origin-top pointer-events-none text-inherit font-medium duration-100 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-250 data-starting-style:ease-in data-ending-style:duration-250 data-ending-style:ease-in pointer-coarse:will-change-[scale,translate,opacity] pointer-coarse:transition-[scale,translate,opacity] pointer-fine:motion-safe:will-change-[scale,translate,filter,opacity] pointer-fine:motion-safe:transition-[scale,translate,filter,opacity] pointer-fine:motion-safe:data-starting-style:blur-sm pointer-fine:motion-safe:data-starting-style:scale-90 pointer-fine:motion-safe:data-ending-style:blur-sm pointer-fine:motion-safe:data-ending-style:scale-90 motion-safe:data-ending-style:-translate-y-1/4 [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] contrast-more:[--media-surface-background-color:oklch(0_0_0)] bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10",
content: "flex justify-between items-center gap-2 px-2.5 py-1 w-full **:mix-blend-difference",
volume: "w-[min(80%,12rem)] *:[--media-progress-fill:var(--media-volume-fill)] *:rounded-[inherit] *:[background-image:linear-gradient(to_right,currentColor_0%,currentColor_var(--media-progress-fill),transparent_var(--media-progress-fill),transparent_100%)] *:[transition:--media-progress-fill_200ms_linear]",
shownVolume: "data-open:duration-100 data-min:animate-media-shake data-max:animate-media-shake motion-reduce:data-min:animate-none motion-reduce:data-max:animate-none",
shownStatus: "data-open:duration-100",
icon: "hidden shrink-0",
shownVolumeHigh: "group-data-[level=high]/input-indicator:block",
shownVolumeLow: "group-data-[level=low]/input-indicator:block",
shownVolumeOff: "group-data-[level=off]/input-indicator:block",
shownCaptionsOn: "group-data-[status=captions-on]/input-indicator:block",
shownCaptionsOff: "group-data-[status=captions-off]/input-indicator:block",
shownFullscreenEnter: "group-data-[status=fullscreen]/input-indicator:block",
shownFullscreenExit: "group-data-[status=exit-fullscreen]/input-indicator:block",
shownPipEnter: "group-data-[status=pip]/input-indicator:block",
shownPipExit: "group-data-[status=exit-pip]/input-indicator:block",
value: "ml-auto",
},
bubble: {
base: "group/input-indicator col-start-2 row-start-1 grid place-content-center text-center p-4 data-direction:gap-1 @2xl/media-root:data-direction:p-6 not-data-direction:bg-black/35 not-data-direction:rounded-full not-data-direction:backdrop-blur-sm not-data-direction:[transition-property:opacity,scale] not-data-direction:duration-200 not-data-direction:ease-out motion-reduce:not-data-direction:transition-opacity motion-reduce:not-data-direction:duration-50 not-data-direction:data-starting-style:opacity-0 not-data-direction:data-ending-style:opacity-0 not-data-direction:data-starting-style:scale-[0.85] not-data-direction:data-ending-style:scale-[0.85] not-data-direction:data-ending-style:duration-100 not-data-direction:data-ending-style:ease-in data-[direction=backward]:col-start-1 data-[direction=backward]:justify-self-start data-[direction=forward]:col-start-3 data-[direction=forward]:justify-self-end",
icon: "hidden size-[calc(var(--media-icon-size)*1.5)]",
shownSeek: "group-data-direction/input-indicator:block group-data-[direction=backward]/input-indicator:[scale:-1_1] motion-safe:transition-[translate,opacity] motion-safe:duration-200 motion-safe:ease-in-out motion-safe:group-data-starting-style/input-indicator:opacity-0 motion-safe:group-data-ending-style/input-indicator:opacity-0 motion-safe:group-data-[direction=forward]/input-indicator:group-data-starting-style/input-indicator:[translate:-60%_0] motion-safe:group-data-[direction=backward]/input-indicator:group-data-starting-style/input-indicator:[translate:60%_0]",
shownPause: "group-data-[status=pause]/input-indicator:block",
shownPlay: "group-data-[status=play]/input-indicator:block group-data-[status=play]/input-indicator:translate-x-px",
time: "tabular-nums",
},
};
const menu = {
root: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.25rem] [--menu-border-radius:0.75rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none overflow-auto! [--media-popover-side-offset:0.5rem] [--media-popover-boundary-offset:0.5rem]",
settings: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.25rem] [--menu-border-radius:0.75rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none [--media-popup-transition:var(--media-popup-base-transition),height_var(--media-popup-transition-timing-function)_var(--menu-transition-duration),width_var(--media-popup-transition-timing-function)_var(--menu-transition-duration)] data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)] data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)] min-w-48 w-(--media-menu-width) h-(--media-menu-height) !overflow-hidden [--media-popover-side-offset:0.5rem] [--media-popover-boundary-offset:0.5rem]",
group: "flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden",
item: "flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50",
separator: "my-1 border-b border-[oklch(0_0_0/0.1)] shadow-[0_1px_0_0_oklch(1_0_0/0.075)]",
tier: "pl-0.5 pt-px text-[0.5625rem] font-semibold leading-none text-current/70",
indicator: "ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100",
icon: "shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]",
rootView: "absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] group/menu-root-view data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur",
submenuPanel: "absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full",
back: "flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full",
hint: "ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65",
hintLabel: "max-w-24 overflow-hidden text-ellipsis whitespace-nowrap",
chevron: "size-3.5",
settingsGroup: "group/settings",
settingsTrigger: "group hidden group-has-[[data-availability=available]]/settings:grid",
settingsIcon: "transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0",
};
const overlay = "absolute inset-0 flex flex-col items-start pointer-events-none rounded-[inherit] opacity-0 bg-linear-to-t from-black/50 via-black/30 via-25% to-transparent backdrop-blur-none backdrop-saturate-100 transition-[opacity,backdrop-filter] duration-(--media-controls-transition-duration) ease-out peer-data-visible/controls:opacity-100 peer-data-visible/buffering:bg-black/35 peer-data-visible/buffering:opacity-100 peer-data-visible/buffering:backdrop-blur-sm peer-data-open/error:opacity-100 peer-data-open/error:duration-(--media-error-dialog-transition-duration) peer-data-open/error:delay-(--media-error-dialog-transition-delay) peer-data-open/error:backdrop-blur-lg peer-data-open/error:backdrop-saturate-150";
const popup = {
popover: "bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset)",
tooltip: "bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)",
volume: "py-3 px-0 rounded-full",
tooltipShortcut: "min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded",
};
const poster = () => "absolute inset-0 w-full h-full pointer-events-none transition-opacity duration-250 not-data-visible:opacity-0 rounded-[inherit] [object-fit:var(--media-object-fit,contain)] [object-position:var(--media-object-position,center)] [&[data-visible]:not([data-loaded])]:opacity-0";
const root = () => "**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,2rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 scrollbar-thin scrollbar-thumb-current/30 [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-auto [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-thumb-current/80 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] bg-black overflow-clip after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10 after:inset-0 after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 [&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&_video]:[object-fit:var(--media-object-fit,contain)] [&_video]:[object-position:var(--media-object-position,center)] [--media-spring-timing-function:linear(0,0.034_1.5%,0.763_9.7%,1.066_13.9%,1.198_19.9%,1.184_21.8%,0.963_37.5%,0.997_50.9%,1)] [--media-video-border-radius:var(--media-border-radius,2rem)] [--media-controls-transition-duration:100ms] [--media-controls-transition-timing-function:ease-out] [--media-error-dialog-transition-duration:350ms] [--media-error-dialog-transition-delay:100ms] [--media-error-dialog-transition-timing-function:var(--media-spring-timing-function)] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-surface-background-color:oklch(1_0_0/0.1)] [--media-surface-inner-border-color:oklch(1_0_0/0.05)] [--media-surface-outer-border-color:oklch(0_0_0/0.1)] [--media-surface-shadow-color:oklch(0_0_0/0.15)] [--media-surface-backdrop-filter:blur(16px)_saturate(1.5)] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-error-dialog-transition-timing-function:ease-out] [--media-tooltip-side-offset:0.75rem] [--media-tooltip-boundary-offset:0.5rem] [--media-popover-side-offset:0.5rem] [--media-popover-boundary-offset:0.5rem] motion-reduce:[--media-popup-transition-duration:0ms] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] contrast-more:[--media-surface-background-color:oklch(0_0_0)] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-inner-border-color:oklch(1_0_0/0.25)] contrast-more:[--media-surface-inner-border-color:oklch(1_0_0/0.25)] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:transparent] contrast-more:[--media-surface-outer-border-color:transparent] pointer-fine:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:300ms] pointer-coarse:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:150ms] motion-reduce:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:50ms] [--media-caption-track-y:-0.5rem] [--media-caption-track-delay:25ms] [--media-caption-track-duration:var(--media-controls-transition-duration)] has-[[data-controls][data-visible]]:[--media-caption-track-y:-5.5rem] @2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:-3.5rem] [&_video::-webkit-media-text-track-container]:transition-[translate] [&_video::-webkit-media-text-track-container]:duration-(--media-caption-track-duration) [&_video::-webkit-media-text-track-container]:ease-out [&_video::-webkit-media-text-track-container]:delay-(--media-caption-track-delay) [&_video::-webkit-media-text-track-container]:translate-y-(--media-caption-track-y) [&_video::-webkit-media-text-track-container]:scale-98 [&_video::-webkit-media-text-track-container]:z-1 [&_video::-webkit-media-text-track-container]:font-[inherit] before:absolute before:inset-0 before:pointer-events-none before:[background-image:var(--media-poster-placeholder,none)] before:bg-no-repeat before:[background-position:var(--media-object-position,center)] before:[background-size:var(--media-object-fit,contain)] before:opacity-0 before:[filter:blur(var(--media-poster-placeholder-blur,20px))] before:transition-opacity before:duration-250 has-[img[data-visible]:not([data-loaded])]:before:opacity-100 [&:fullscreen]:[--media-border-radius:0] [&:fullscreen_video]:object-contain";
const seek = {
label: "text-[10px] font-medium tracking-tighter tabular-nums",
labelForward: "absolute -right-px -bottom-0.75",
labelBackward: "absolute -left-px -bottom-0.75",
};
const slider = {
root: "group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20",
track: "relative isolate overflow-hidden rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1 data-[orientation=vertical]:h-full bg-white/20 ring-1 ring-black/5",
fill: {
base: "absolute rounded-[inherit] pointer-events-none",
fill: "bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)",
buffer: "bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)",
},
thumb: {
base: "z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.35),0_1px_2px_-1px_oklch(0_0_0/0.35)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/15 hover:outline-offset-0 focus-visible:outline-current/15 focus-visible:outline-offset-0 after:absolute after:-inset-1 after:rounded-[inherit] after:shadow-[0_0_0_2px_currentColor] after:pointer-events-none after:transition-[opacity,scale] after:duration-150 after:ease-out after:opacity-0 after:scale-50 focus-visible:after:opacity-100 focus-visible:after:scale-100 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))]",
persistent: "size-3",
interactive: "size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3",
},
preview: "group/preview before:block before:min-w-1 before:h-1 before:bg-current before:rounded-full before:opacity-0 before:scale-50 before:shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_2px_0_oklch(0_0_0/0.35)] before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden",
value: "absolute bottom-9 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none",
};
const thumbnail = {
root: "group/thumbnail peer/thumbnail pointer-events-none bg-black/90 rounded-xl bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-slider-thumbnail-max-width:11rem] [--media-slider-thumbnail-max-height:8rem] [--media-slider-thumbnail-padding:-1.125rem] [--media-slider-thumbnail-inset:calc((100cqi-100%)/2)] absolute [left:clamp(calc(var(--media-slider-thumbnail-max-width)/2+var(--media-slider-thumbnail-padding)-var(--media-slider-thumbnail-inset)),var(--media-slider-pointer),calc(100%-var(--media-slider-thumbnail-max-width)/2-var(--media-slider-thumbnail-padding)+var(--media-slider-thumbnail-inset)))] bottom-[calc(100%+1.2rem)] -translate-x-1/2 opacity-0 scale-80 blur-sm origin-bottom transition-[scale,opacity,filter] duration-150 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:opacity-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:scale-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:blur-none",
image: "block relative overflow-clip rounded-[inherit] transition-opacity duration-150 ease-out after:absolute after:inset-0 after:rounded-[inherit] after:bg-linear-to-t after:from-black/80 after:via-black/30 after:to-black/0 data-loading:opacity-0 max-w-(--media-slider-thumbnail-max-width) max-h-(--media-slider-thumbnail-max-height)",
time: "absolute bottom-2 inset-x-0 text-center tabular-nums",
spinner: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity duration-150 ease-out group-not-has-[[role=img][data-loading]]/thumbnail:[--media-spinner-animation:none] group-has-[[role=img][data-loading]]/thumbnail:opacity-100",
};
const time = {
group: "@container/media-time flex items-center flex-1 gap-3 px-2 grow-0 shrink-0 basis-full order-[-1] px-2.5 @2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]",
current: "hidden @2xs/media-time:block tabular-nums",
duration: "tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2",
};
interface VideoSkinProps {
src: string;
style?: CSSProperties;
className?: string;
poster?: string | RenderProp<Poster.State> | undefined;
}
const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;
const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;
function MenuChevron({ flipped = false }: { flipped?: boolean }): ReactNode {
return <ChevronIcon className={`${icon} ${menu.icon} ${menu.chevron} ${flipped ? iconFlipped : undefined}`} />;
}
function SettingsMenu(): ReactNode {
const playbackRate = usePlaybackRateOptions();
const quality = useQualityOptions();
const audioTrack = useAudioTrackOptions();
const captions = useCaptionsOptions();
const hasPlaybackRate = playbackRate?.state.availability === 'available';
const hasQuality = quality?.state.availability === 'available';
const hasAudioTrack = audioTrack?.state.availability === 'available';
const hasCaptions = captions?.state.availability === 'available';
if (!hasPlaybackRate && !hasQuality && !hasAudioTrack && !hasCaptions) return null;
return (
<Menu.Root side="top" align="center">
<Menu.Trigger
aria-label="Settings"
className="media-button--settings"
render={<Button className={`${button.icon} ${menu.settingsTrigger}`} />}
>
<GearIcon className={`${icon} ${menu.settingsIcon}`} />
</Menu.Trigger>
<Menu.Content className={menu.settings}>
<Menu.View className={menu.rootView}>
<div className={menu.group}>
{hasQuality ? (
<Menu.Root>
<Menu.Trigger
type="quality"
className={`${menu.item} media-menu__item--submenu`}
render={(props) => (
<div {...props}>
<QualityIcon className={`${icon} ${menu.icon}`} />
<span>Quality</span>
<span className={menu.hint}>
<Menu.ItemValue className={menu.hintLabel} />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className={menu.submenuPanel}>
<Menu.Back className={menu.back}>
<MenuChevron flipped />
Quality
</Menu.Back>
<Menu.Separator className={menu.separator} />
<Menu.RadioGroup
className={menu.group}
value={quality.value}
onValueChange={quality.setValue}
aria-label="Quality"
>
{quality.options.map((option) => (
<Menu.RadioItem
key={option.value}
className={menu.item}
value={option.value}
disabled={option.disabled}
>
<span>
{option.label}
{option.tier ? <sup className={menu.tier}>{option.tier}</sup> : null}
</span>
{option.badge ? <span className={badge}>{option.badge}</span> : null}
<Menu.ItemIndicator
checked={option.value === quality.value}
forceMount
className={menu.indicator}
>
<CheckIcon className={`${icon} ${menu.icon}`} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasAudioTrack ? (
<Menu.Root>
<Menu.Trigger
type="audio-track"
className={`${menu.item} media-menu__item--submenu`}
render={(props) => (
<div {...props}>
<SpeechIcon className={icon} />
<span>Audio</span>
<span className={menu.hint}>
<Menu.ItemValue className={menu.hintLabel} />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className={menu.submenuPanel}>
<Menu.Back className={menu.back}>
<MenuChevron flipped />
Audio
</Menu.Back>
<Menu.Separator className={menu.separator} />
<Menu.RadioGroup
className={menu.group}
value={audioTrack.value}
onValueChange={audioTrack.setValue}
aria-label="Audio tracks"
>
{audioTrack.options.map((option) => (
<Menu.RadioItem
key={option.value}
className={menu.item}
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === audioTrack.value}
forceMount
className={menu.indicator}
>
<CheckIcon className={icon} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasPlaybackRate ? (
<Menu.Root>
<Menu.Trigger
type="playback-rate"
className={`${menu.item} media-menu__item--submenu`}
render={(props) => (
<div {...props}>
<SpeedIcon className={`${icon} ${menu.icon}`} />
<span>Speed</span>
<span className={menu.hint}>
<Menu.ItemValue className={menu.hintLabel} />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className={menu.submenuPanel}>
<Menu.Back className={menu.back}>
<MenuChevron flipped />
Speed
</Menu.Back>
<Menu.Separator className={menu.separator} />
<Menu.RadioGroup
className={menu.group}
value={playbackRate.value}
onValueChange={playbackRate.setValue}
aria-label="Playback rate"
>
{playbackRate.options.map((option) => (
<Menu.RadioItem
key={option.value}
className={menu.item}
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === playbackRate.value}
forceMount
className={menu.indicator}
>
<CheckIcon className={`${icon} ${menu.icon}`} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasCaptions ? (
<Menu.Root>
<Menu.Trigger
type="captions"
className={`${menu.item} media-menu__item--submenu`}
render={(props) => (
<div {...props}>
<CaptionsOffIcon className={`${icon} ${menu.icon}`} />
<span>Captions</span>
<span className={menu.hint}>
<Menu.ItemValue className={menu.hintLabel} />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className={menu.submenuPanel}>
<Menu.Back className={menu.back}>
<MenuChevron flipped />
Captions
</Menu.Back>
<Menu.Separator className={menu.separator} />
<Menu.RadioGroup
className={menu.group}
value={captions.value}
onValueChange={captions.setValue}
aria-label="Captions"
>
{captions.options.map((option) => (
<Menu.RadioItem
key={option.value}
className={menu.item}
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === captions.value}
forceMount
className={menu.indicator}
>
<CheckIcon className={`${icon} ${menu.icon}`} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
</div>
</Menu.View>
</Menu.Content>
</Menu.Root>
);
}
export function VideoSkinTailwind({ children, className, poster: posterProp, placeholder, style, ...rest }: VideoSkinProps): ReactNode {
const containerStyle = placeholder
? ({ '--media-poster-placeholder': `url(${placeholder})`, ...style } as CSSProperties)
: style;
return (
<Container className={`${root(false)} ${className ?? ''}`} style={containerStyle} {...rest}>
{children}
{posterProp && (
<Poster
src={isString(posterProp) ? posterProp : undefined}
render={isRenderProp(posterProp) ? posterProp : undefined}
className={poster(false)}
/>
)}
<BufferingIndicator
render={(props) => (
<div {...props} className={bufferingIndicator.root}>
<SpinnerIcon className={icon} />
</div>
)}
/>
<ErrorDialog.Root>
<ErrorDialog.Popup className={error.root}>
<div className={error.dialog}>
<div className={error.content}>
<ErrorDialog.Title className={error.title}>Something went wrong.</ErrorDialog.Title>
<ErrorDialog.Description className={error.description} />
</div>
<div className={error.actions}>
<ErrorDialog.Close className={`${button.base} ${button.primary}`}>OK</ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root
data-controls="" // Used as a hook for Tailwind has-[] styles
className={controls}
>
<Tooltip.Provider>
<div className={buttonGroupStart}>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PlayButton className={iconState.play.button} render={<Button />}>
<RestartIcon className={`${icon} ${iconState.play.restart}`} />
<PlayIcon className={`${icon} ${iconState.play.play}`} />
<PauseIcon className={`${icon} ${iconState.play.pause}`} />
</PlayButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={`${icon} ${iconFlipped}`} />
<span className={`${seek.label} ${seek.labelBackward}`}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={icon} />
<span className={`${seek.label} ${seek.labelForward}`}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
</div>
<div className={time.group}>
<Time.Value type="current" className={time.current} />
<TimeSlider.Root render={<SliderRoot />}>
<TimeSlider.Track render={<SliderTrack />}>
<TimeSlider.Fill render={<SliderFill />} />
<TimeSlider.Buffer render={<SliderBuffer />} />
</TimeSlider.Track>
<TimeSlider.Thumb render={<SliderThumb />} />
<div className={thumbnail.root}>
<Slider.Thumbnail className={thumbnail.image} />
<TimeSlider.Value type="pointer" className={thumbnail.time} />
<SpinnerIcon className={`${icon} ${thumbnail.spinner}`} />
</div>
<TimeSlider.Preview className={slider.preview}>
<TimeSlider.Value type="pointer" className={slider.value} />
</TimeSlider.Preview>
</TimeSlider.Root>
<Time.Value toggle type="remaining" className={time.duration} />
</div>
<div className={`${buttonGroupEnd} ${menu.settingsGroup}`}>
<VolumePopover />
<SettingsMenu />
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CastButton className={iconState.cast.button} render={<Button />}>
<CastEnterIcon className={`${icon} ${iconState.cast.enter}`} />
<CastExitIcon className={`${icon} ${iconState.cast.exit}`} />
</CastButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<AirPlayButton className={iconState.airplay.button} render={<Button />}>
<AirPlayEnterIcon className={`${icon} ${iconState.airplay.enter}`} />
<AirPlayExitIcon className={`${icon} ${iconState.airplay.exit}`} />
</AirPlayButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PiPButton className={iconState.pip.button} render={<Button />}>
<PipEnterIcon className={`${icon} ${iconState.pip.off}`} />
<PipExitIcon className={`${icon} ${iconState.pip.on}`} />
</PiPButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<FullscreenButton className={iconState.fullscreen.button} render={<Button />}>
<FullscreenEnterIcon className={`${icon} ${iconState.fullscreen.enter}`} />
<FullscreenExitIcon className={`${icon} ${iconState.fullscreen.exit}`} />
</FullscreenButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
</div>
</Tooltip.Provider>
</Controls.Root>
<div className={overlay} />
{/* Hotkeys */}
<Hotkey keys="Space" action="togglePaused" />
<Hotkey keys="k" action="togglePaused" />
<Hotkey keys="m" action="toggleMuted" />
<Hotkey keys="f" action="toggleFullscreen" />
<Hotkey keys="c" action="toggleSubtitles" />
<Hotkey keys="i" action="togglePictureInPicture" />
<Hotkey keys="ArrowRight" action="seekStep" value={SEEK_TIME / 2} />
<Hotkey keys="ArrowLeft" action="seekStep" value={-(SEEK_TIME / 2)} />
<Hotkey keys="l" action="seekStep" value={SEEK_TIME} />
<Hotkey keys="j" action="seekStep" value={-SEEK_TIME} />
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
<Hotkey keys="0-9" action="seekToPercent" />
<Hotkey keys="Home" action="seekToPercent" value={0} />
<Hotkey keys="End" action="seekToPercent" value={100} />
<Hotkey keys=">" action="speedUp" />
<Hotkey keys="<" action="speedDown" />
{/* Gestures */}
<Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
<Gesture type="tap" action="toggleControls" pointer="touch" />
<Gesture type="doubletap" action="seekStep" value={-SEEK_TIME} region="left" />
<Gesture type="doubletap" action="toggleFullscreen" region="center" />
<Gesture type="doubletap" action="seekStep" value={SEEK_TIME} region="right" />
{/* Input Feedback */}
<StatusAnnouncer />
<div className={inputFeedback.root}>
<VolumeIndicator.Root
className={`${inputFeedback.island.base} ${inputFeedback.island.volume} ${inputFeedback.island.shownVolume}`}
>
<VolumeIndicator.Fill className={inputFeedback.island.content}>
<VolumeHighIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeHigh}`} />
<VolumeLowIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeLow}`} />
<VolumeOffIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeOff}`} />
<VolumeIndicator.Value className={inputFeedback.island.value} />
</VolumeIndicator.Fill>
</VolumeIndicator.Root>
<StatusIndicator.Root
actions={TOP_STATUS_ACTIONS}
className={`${inputFeedback.island.base} ${inputFeedback.island.shownStatus}`}
>
<div className={inputFeedback.island.content}>
<CaptionsOnIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownCaptionsOn}`} />
<CaptionsOffIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownCaptionsOff}`} />
<FullscreenEnterIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownFullscreenEnter}`} />
<FullscreenExitIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownFullscreenExit}`} />
<PipEnterIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownPipEnter}`} />
<PipExitIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownPipExit}`} />
<StatusIndicator.Value className={inputFeedback.island.value} />
</div>
</StatusIndicator.Root>
<SeekIndicator.Root className={inputFeedback.bubble.base}>
<ChevronIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownSeek}`} />
<SeekIndicator.Value className={inputFeedback.bubble.time} />
</SeekIndicator.Root>
<StatusIndicator.Root actions={CENTER_STATUS_ACTIONS} className={inputFeedback.bubble.base}>
<PlayIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownPlay}`} />
<PauseIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownPause}`} />
</StatusIndicator.Root>
</div>
</Container>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
// ================================================================
// Components
// ================================================================
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
return (
<button ref={ref} type="button" className={`${button.base} ${button.subtle} ${button.icon} ${className ?? ''}`} {...props} />
);
});
const SliderRoot = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderRoot({ className, ...props }, ref) {
return <div ref={ref} className={`${slider.root} ${className ?? ''}`} {...props} />;
});
const SliderTrack = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderTrack(
{ className, ...props },
ref
) {
return <div ref={ref} className={`${slider.track} ${className ?? ''}`} {...props} />;
});
const SliderFill = forwardRef<HTMLDivElement, ComponentProps<'div'> & { type?: 'fill' | 'buffer' }>(function SliderFill(
{ type = 'fill', className, ...props },
ref
) {
return (
<div
ref={ref}
className={`${slider.fill.base} ${type === 'fill' ? slider.fill.fill : slider.fill.buffer} ${className ?? ''}`}
{...props}
/>
);
});
const SliderBuffer = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderBuffer(props, ref) {
return <SliderFill type="buffer" ref={ref} {...props} />;
});
const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persistent?: boolean }>(function SliderThumb(
{ persistent, className, ...props },
ref
) {
return (
<div
ref={ref}
className={`${slider.thumb.base} ${persistent ? slider.thumb.persistent : slider.thumb.interactive} ${className ?? ''}`}
{...props}
/>
);
});
function VolumePopover(): ReactNode {
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
const muteButton = (
<MuteButton className={iconState.mute.button} render={<Button />}>
<VolumeOffIcon className={`${icon} ${iconState.mute.volumeOff}`} />
<VolumeLowIcon className={`${icon} ${iconState.mute.volumeLow}`} />
<VolumeHighIcon className={`${icon} ${iconState.mute.volumeHigh}`} />
</MuteButton>
);
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top">
<Popover.Trigger render={muteButton} />
<Popover.Popup className={`${popup.popover} ${popup.volume}`}>
<VolumeSlider.Root orientation="vertical" thumbAlignment="edge" render={<SliderRoot />}>
<VolumeSlider.Track render={<SliderTrack />}>
<VolumeSlider.Fill render={<SliderFill />} />
</VolumeSlider.Track>
<VolumeSlider.Thumb render={(props) => <SliderThumb persistent {...props} />} />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Root>
);
}
// ================================================================
// Utilities
// ================================================================
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isRenderProp(value: unknown): value is RenderProp<unknown> {
return typeof value === 'function' || isValidElement(value);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/video-ui.js"></script>
<link rel="stylesheet" href="./player.css">
<video-player>
<media-container class="media-default-skin media-default-skin--video">
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" playsinline></video>
<media-poster>
<img src="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp" />
</media-poster>
<media-buffering-indicator class="media-buffering-indicator">
<media-icon name="spinner" class="media-icon"></media-icon>
</media-buffering-indicator>
<media-error-dialog class="media-error">
<div class="media-error__dialog media-surface">
<div class="media-error__content">
<media-alert-dialog-title class="media-error__title">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="media-error__description"></media-alert-dialog-description>
</div>
<div class="media-error__actions">
<media-alert-dialog-close class="media-button media-button--primary">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<media-controls class="media-surface media-controls">
<media-tooltip-group>
<div class="media-button-group">
<media-play-button commandfor="play-tooltip" class="media-button media-button--subtle media-button--icon media-button--play">
<media-icon name="restart" class="media-icon media-icon--restart"></media-icon>
<media-icon name="play" class="media-icon media-icon--play"></media-icon>
<media-icon name="pause" class="media-icon media-icon--pause"></media-icon>
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-backward-tooltip" seconds="-10" class="media-button media-button--subtle media-button--icon media-button--seek">
<span class="media-icon__container">
<media-icon name="seek" class="media-icon media-icon--flipped"></media-icon>
<span class="media-icon__label">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-backward-tooltip" side="top" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-forward-tooltip" seconds="10" class="media-button media-button--subtle media-button--icon media-button--seek">
<span class="media-icon__container">
<media-icon name="seek" class="media-icon"></media-icon>
<span class="media-icon__label">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-forward-tooltip" side="top" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
</div>
<div class="media-time-controls">
<media-time type="current" class="media-time"></media-time>
<media-time-slider class="media-slider">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
<media-slider-buffer class="media-slider__buffer"></media-slider-buffer>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb"></media-slider-thumb>
<div class="media-surface media-thumbnail media-slider__thumbnail">
<media-slider-thumbnail class="media-thumbnail__image"></media-slider-thumbnail>
<media-slider-value type="pointer" class="media-time media-thumbnail__time"></media-slider-value>
<media-icon name="spinner" class="media-thumbnail__spinner media-icon"></media-icon>
</div>
<media-slider-preview class="media-slider__preview">
<media-slider-value type="pointer" class="media-slider__value media-time"></media-slider-value>
</media-slider-preview>
</media-time-slider>
<media-time toggle type="remaining" class="media-time"></media-time>
</div>
<div class="media-button-group">
<media-mute-button commandfor="video-volume-popover" class="media-button media-button--subtle media-button--icon media-button--mute">
<media-icon name="volume-off" class="media-icon media-icon--volume-off"></media-icon>
<media-icon name="volume-low" class="media-icon media-icon--volume-low"></media-icon>
<media-icon name="volume-high" class="media-icon media-icon--volume-high"></media-icon>
</media-mute-button>
<media-popover id="video-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="media-surface media-popover media-popover--volume">
<media-volume-slider class="media-slider" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb media-slider__thumb--persistent"></media-slider-thumb>
</media-volume-slider>
</media-popover>
<button commandfor="settings-menu" aria-label="Settings" class="media-button media-button--subtle media-button--icon media-button--settings">
<media-icon name="gear" class="media-icon media-icon--settings"></media-icon>
</button>
<media-menu id="settings-menu" side="top" align="center" class="media-surface media-popover media-menu media-menu--settings">
<media-menu-view class="media-menu__panel">
<div class="media-menu__group">
<media-menu-item commandfor="settings-quality-menu" type="quality" data-setting="quality" class="media-menu__item media-menu__item--submenu">
<media-icon name="switches" class="media-icon"></media-icon>
<span>Quality</span>
<span class="media-menu__hint">
<media-menu-item-value class="media-menu__hint-label"></media-menu-item-value>
<media-icon name="chevron" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-audio-menu" type="audio-track" data-setting="audio-track" class="media-menu__item media-menu__item--submenu">
<media-icon name="speech" class="media-icon"></media-icon>
<span>Audio</span>
<span class="media-menu__hint">
<media-menu-item-value class="media-menu__hint-label"></media-menu-item-value>
<media-icon name="chevron" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-speed-menu" type="playback-rate" data-setting="playback-rate" class="media-menu__item media-menu__item--submenu">
<media-icon name="speed" class="media-icon"></media-icon>
<span>Speed</span>
<span class="media-menu__hint">
<media-menu-item-value class="media-menu__hint-label"></media-menu-item-value>
<media-icon name="chevron" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-captions-menu" type="captions" data-setting="captions" class="media-menu__item media-menu__item--submenu">
<media-icon name="captions-off" class="media-icon"></media-icon>
<span>Captions</span>
<span class="media-menu__hint">
<media-menu-item-value class="media-menu__hint-label"></media-menu-item-value>
<media-icon name="chevron" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
</div>
</media-menu-view>
<media-menu id="settings-quality-menu" class="media-menu__panel">
<media-menu-back class="media-menu__back">
<media-icon name="chevron" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
Quality
</media-menu-back>
<div class="media-menu__separator"></div>
<media-quality-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span>
<span data-part="label"></span>
<sup data-part="tier" class="media-menu__tier"></sup>
</span>
<span data-part="badge" class="media-badge"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-quality-radio-group>
</media-menu>
<media-menu id="settings-audio-menu" class="media-menu__panel">
<media-menu-back class="media-menu__back">
<media-icon name="chevron" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
Audio
</media-menu-back>
<div class="media-menu__separator"></div>
<media-audio-track-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-audio-track-radio-group>
</media-menu>
<media-menu id="settings-speed-menu" class="media-menu__panel">
<media-menu-back class="media-menu__back">
<media-icon name="chevron" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
Speed
</media-menu-back>
<div class="media-menu__separator"></div>
<media-playback-rate-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
</media-menu>
<media-menu id="settings-captions-menu" class="media-menu__panel">
<media-menu-back class="media-menu__back">
<media-icon name="chevron" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
Captions
</media-menu-back>
<div class="media-menu__separator"></div>
<media-captions-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-captions-radio-group>
</media-menu>
</media-menu>
<media-cast-button commandfor="cast-tooltip" class="media-button media-button--subtle media-button--icon media-button--cast">
<media-icon name="cast-enter" class="media-icon media-icon--cast-enter"></media-icon>
<media-icon name="cast-exit" class="media-icon media-icon--cast-exit"></media-icon>
</media-cast-button>
<media-tooltip id="cast-tooltip" side="top" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-airplay-button commandfor="airplay-tooltip" class="media-button media-button--subtle media-button--icon media-button--airplay">
<media-icon name="airplay-enter" class="media-icon media-icon--airplay-enter"></media-icon>
<media-icon name="airplay-exit" class="media-icon media-icon--airplay-exit"></media-icon>
</media-airplay-button>
<media-tooltip id="airplay-tooltip" side="top" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-pip-button commandfor="pip-tooltip" class="media-button media-button--subtle media-button--icon media-button--pip">
<media-icon name="pip-enter" class="media-icon media-icon--pip-enter"></media-icon>
<media-icon name="pip-exit" class="media-icon media-icon--pip-exit"></media-icon>
</media-pip-button>
<media-tooltip id="pip-tooltip" side="top" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-fullscreen-button commandfor="fullscreen-tooltip" class="media-button media-button--subtle media-button--icon media-button--fullscreen">
<media-icon name="fullscreen-enter" class="media-icon media-icon--fullscreen-enter"></media-icon>
<media-icon name="fullscreen-exit" class="media-icon media-icon--fullscreen-exit"></media-icon>
</media-fullscreen-button>
<media-tooltip id="fullscreen-tooltip" side="top" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
</div>
</media-tooltip-group>
</media-controls>
<div class="media-overlay"></div>
<!-- Hotkeys -->
<media-hotkey keys="Space" action="togglePaused"></media-hotkey>
<media-hotkey keys="k" action="togglePaused"></media-hotkey>
<media-hotkey keys="m" action="toggleMuted"></media-hotkey>
<media-hotkey keys="f" action="toggleFullscreen"></media-hotkey>
<media-hotkey keys="c" action="toggleSubtitles"></media-hotkey>
<media-hotkey keys="i" action="togglePictureInPicture"></media-hotkey>
<media-hotkey keys="ArrowRight" action="seekStep" value="5"></media-hotkey>
<media-hotkey keys="ArrowLeft" action="seekStep" value="-5"></media-hotkey>
<media-hotkey keys="l" action="seekStep" value="10"></media-hotkey>
<media-hotkey keys="j" action="seekStep" value="-10"></media-hotkey>
<media-hotkey keys="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
<media-hotkey keys="0-9" action="seekToPercent"></media-hotkey>
<media-hotkey keys="Home" action="seekToPercent" value="0"></media-hotkey>
<media-hotkey keys="End" action="seekToPercent" value="100"></media-hotkey>
<media-hotkey keys=">" action="speedUp"></media-hotkey>
<media-hotkey keys="<" action="speedDown"></media-hotkey>
<!-- Gestures -->
<media-gesture type="tap" action="togglePaused" pointer="mouse" region="center"></media-gesture>
<media-gesture type="tap" action="toggleControls" pointer="touch"></media-gesture>
<media-gesture type="doubletap" action="seekStep" value="-10" region="left"></media-gesture>
<media-gesture type="doubletap" action="toggleFullscreen" region="center"></media-gesture>
<media-gesture type="doubletap" action="seekStep" value="10" region="right"></media-gesture>
<!-- Input Feedback -->
<media-status-announcer></media-status-announcer>
<div class="media-input-feedback">
<media-volume-indicator hidden class="media-surface media-input-feedback-island media-input-feedback-island--volume">
<media-volume-indicator-fill class="media-input-feedback-island__content">
<media-icon name="volume-high" class="media-icon media-icon--volume-high"></media-icon>
<media-icon name="volume-low" class="media-icon media-icon--volume-low"></media-icon>
<media-icon name="volume-off" class="media-icon media-icon--volume-off"></media-icon>
<media-volume-indicator-value class="media-input-feedback-island__value"></media-volume-indicator-value>
</media-volume-indicator-fill>
</media-volume-indicator>
<media-status-indicator
hidden
actions="toggleSubtitles toggleFullscreen togglePictureInPicture"
class="media-surface media-input-feedback-island media-input-feedback-island--status"
>
<div class="media-input-feedback-island__content">
<media-icon name="captions-on" class="media-icon media-icon--captions-on"></media-icon>
<media-icon name="captions-off" class="media-icon media-icon--captions-off"></media-icon>
<media-icon name="fullscreen-enter" class="media-icon media-icon--fullscreen-enter"></media-icon>
<media-icon name="fullscreen-exit" class="media-icon media-icon--fullscreen-exit"></media-icon>
<media-icon name="pip-enter" class="media-icon media-icon--pip-enter"></media-icon>
<media-icon name="pip-exit" class="media-icon media-icon--pip-exit"></media-icon>
<media-status-indicator-value class="media-input-feedback-island__value"></media-status-indicator-value>
</div>
</media-status-indicator>
<media-seek-indicator hidden class="media-input-feedback-bubble">
<media-icon name="chevron" class="media-icon media-icon--seek"></media-icon>
<media-seek-indicator-value class="media-time"></media-seek-indicator-value>
</media-seek-indicator>
<media-status-indicator hidden actions="togglePaused" class="media-input-feedback-bubble">
<media-icon name="play" class="media-icon media-icon--play"></media-icon>
<media-icon name="pause" class="media-icon media-icon--pause"></media-icon>
</media-status-indicator>
</div>
</media-container>
</video-player>
/* -------------------------------------------------------------------------- */
/* Global styles for the host document, outside of the Shadow DOM */
/* -------------------------------------------------------------------------- */
video-player,
live-video-player {
display: contents;
}
/*
Required to override any default video and image styles (such as
Tailwind's CSS reset) and ensure they fill the container as expected.
*/
video-player video,
video-player [slot="poster"],
live-video-player video,
live-video-player [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
video-player video::-webkit-media-text-track-container,
live-video-player video::-webkit-media-text-track-container {
z-index: 1;
font-family: inherit;
scale: 0.98;
translate: 0 var(--media-caption-track-y, 0);
transition: translate var(--media-caption-track-duration, 0) ease-out;
transition-delay: var(--media-caption-track-delay, 0);
}
/* -------------------------------------------------------------------------- */
/* Shared styles for all HTML skins */
/* -------------------------------------------------------------------------- */
media-tooltip-group {
display: contents;
}
:host {
/* `display:grid` fixes a weird issue with Safari when setting aspect-ratio */
display: grid;
width: 100%;
}
media-container {
min-width: 0;
min-height: 0;
}
/* Hide volume popover when volume control is unsupported (e.g., iOS Safari). */
.media-popover--volume:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
/* ==========================================================================
Reset
========================================================================== */
.media-default-skin *,
.media-default-skin *::before,
.media-default-skin *::after {
box-sizing: border-box;
}
.media-default-skin img,
.media-default-skin video,
.media-default-skin svg {
display: block;
max-width: 100%;
}
.media-default-skin button {
font: inherit;
}
.media-default-skin [hidden][hidden] {
/* Keep authored templates hidden even when component classes set display. */
display: none;
}
@media (prefers-reduced-motion: no-preference) {
.media-default-skin {
interpolate-size: allow-keywords;
}
}
/* ==========================================================================
Root Container
========================================================================== */
.media-default-skin {
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
--media-icon-size: 18px;
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
position: relative;
display: block;
width: 100%;
height: 100%;
container: media-root / inline-size;
font-family:
Inter Variable,
Inter,
ui-sans-serif,
system-ui,
sans-serif;
font-size: 0.8125rem; /* 13px at 100% font size */
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
scrollbar-width: thin;
border-radius: var(--media-border-radius, 2rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-color-scrollbar-thumb);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
/* ==========================================================================
Surface (shared glass effect for tooltips, popovers, controls)
========================================================================== */
.media-default-skin .media-surface {
background-color: var(--media-surface-background-color);
box-shadow:
0 0 0 1px var(--media-surface-outer-border-color),
0 1px 3px 0 var(--media-surface-shadow-color),
0 1px 2px -1px var(--media-surface-shadow-color);
backdrop-filter: var(--media-surface-backdrop-filter);
/* Inner border ring */
&::after {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
content: "";
border-radius: inherit;
box-shadow: inset 0 0 0 1px var(--media-surface-inner-border-color);
}
}
/* ==========================================================================
Media Element
========================================================================== */
.media-default-skin ::slotted(video),
.media-default-skin video {
display: block;
width: 100%;
height: 100%;
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
}
.media-default-skin ::slotted(video) {
border-radius: var(--media-video-border-radius);
}
.media-default-skin video {
border-radius: inherit;
}
.media-default-skin:fullscreen ::slotted(video),
.media-default-skin:fullscreen video {
object-fit: contain;
}
/* ==========================================================================
Overlay / Scrim
========================================================================== */
.media-default-skin .media-overlay {
position: absolute;
inset: 0;
pointer-events: none;
background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.3) 25%, oklch(0 0 0 / 0));
border-radius: inherit;
opacity: 0;
backdrop-filter: blur(0) saturate(1);
transition-timing-function: ease-out;
transition-duration: var(--media-controls-transition-duration);
transition-property: opacity, backdrop-filter;
}
.media-default-skin .media-error ~ .media-overlay {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-default-skin .media-controls[data-visible] ~ .media-overlay,
.media-default-skin .media-error[data-open] ~ .media-overlay {
opacity: 1;
}
.media-default-skin .media-buffering-indicator[data-visible] ~ .media-overlay {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-default-skin .media-error[data-open] ~ .media-overlay {
backdrop-filter: blur(16px) saturate(1.5);
}
/* ==========================================================================
Buffering Indicator
========================================================================== */
.media-default-skin .media-buffering-indicator {
position: absolute;
inset: 0;
z-index: 10;
display: none;
place-content: center;
color: oklch(1 0 0);
pointer-events: none;
&:not([data-visible]) {
--media-spinner-animation: none;
}
&[data-visible] {
display: grid;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-default-skin .media-error {
outline: none;
}
.media-default-skin .media-error:not([data-open]) {
display: none;
}
.media-default-skin .media-error__title {
font-weight: 600;
line-height: 1.25;
}
.media-default-skin .media-error__description {
overflow-wrap: anywhere;
opacity: 0.7;
}
.media-default-skin .media-error__actions {
display: flex;
gap: 0.5rem;
& > * {
flex: 1;
}
}
.media-default-skin .media-error[data-open] ~ .media-controls * {
visibility: hidden;
}
/* ==========================================================================
Controls
========================================================================== */
.media-default-skin .media-controls {
display: flex;
column-gap: 0.075rem;
align-items: center;
padding: 0.375rem;
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
border-radius: 1.5rem;
}
/* ==========================================================================
Time Display
========================================================================== */
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: 0.75rem;
align-items: center;
padding-inline: 0.5rem;
container: media-time-controls / inline-size;
}
.media-default-skin .media-time {
font-variant-numeric: tabular-nums;
}
.media-default-skin .media-time[role="button"] {
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: 0.25rem;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
/* ==========================================================================
Buttons
========================================================================== */
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
min-height: 0;
padding: 0.5rem 1rem;
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(infinity * 1px);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:active {
scale: 0.98;
}
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: oklch(0 0 0);
text-shadow: none;
background: oklch(1 0 0);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
text-decoration: none;
background-color: oklch(from currentColor l c h / 0.1);
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
width: 2.25rem;
aspect-ratio: 1;
padding: 0;
&:active {
scale: 0.9;
}
& .media-icon__container {
display: grid;
}
& .media-icon {
grid-area: 1 / 1;
transition-behavior: allow-discrete;
transition-property: display, opacity;
transition-duration: 150ms;
transition-timing-function: ease-out;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
/* Seek button */
.media-default-skin .media-button--seek {
& .media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 10px;
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
}
&:has(.media-icon--flipped) .media-icon__label {
right: unset;
left: -1px;
}
}
/* Playback rate button */
.media-default-skin .media-button--playback-rate {
padding: 0;
font-variant-numeric: tabular-nums;
&::after {
width: 4ch;
content: attr(data-rate) "\00D7";
}
&[data-inline-rate-label]::after {
content: none;
}
}
/* Settings button */
.media-default-skin .media-button--settings {
display: none;
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
&[aria-expanded="true"] .media-icon--settings {
transform: rotate(90deg);
}
}
.media-default-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
display: grid;
}
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
text content. */
.media-default-skin .media-button--live {
display: inline-flex;
gap: 0.4rem;
align-items: center;
width: auto;
aspect-ratio: auto;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
transition: background-color 150ms ease-out;
}
&[data-live-edge]::before {
background-color: oklch(0.65 0.22 27);
}
}
/* ==========================================================================
Button Groups
========================================================================== */
.media-default-skin .media-button-group {
display: flex;
gap: 0.075rem;
align-items: center;
@container media-root (width > 42rem) {
gap: 0.125rem;
}
}
/* ==========================================================================
Badge
========================================================================== */
.media-default-skin .media-badge {
padding: 0.125rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
border-radius: calc(infinity * 1px);
}
/* ==========================================================================
Icons
========================================================================== */
.media-default-skin .media-icon__container {
position: relative;
}
.media-default-skin .media-icon {
flex-shrink: 0;
width: var(--media-icon-size);
height: var(--media-icon-size);
}
.media-default-skin .media-icon--flipped {
scale: -1 1;
}
/* ==========================================================================
Poster Image
========================================================================== */
.media-default-skin media-poster,
.media-default-skin > img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
transition: opacity 0.25s;
}
.media-default-skin media-poster:not([data-visible]),
.media-default-skin > img:not([data-visible]),
.media-default-skin > img[data-visible]:not([data-loaded]) {
opacity: 0;
}
.media-default-skin media-poster ::slotted(img),
.media-default-skin media-poster img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: var(--media-video-border-radius);
}
.media-default-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
/* Blurred placeholder: HTML path targets media-poster::before, React path targets the skin container::before */
.media-default-skin media-poster::before,
.media-default-skin::before {
position: absolute;
inset: 0;
pointer-events: none;
content: "";
background-image: var(--media-poster-placeholder, none);
background-repeat: no-repeat;
background-position: var(--media-object-position, center);
background-size: var(--media-object-fit, contain);
filter: blur(var(--media-poster-placeholder-blur, 20px));
}
/* React path: hidden by default, transitions in while poster loads */
.media-default-skin::before {
opacity: 0;
transition: opacity 0.25s;
}
/* Show placeholder while the poster is visible but not yet loaded */
.media-default-skin:has(img[data-visible]:not([data-loaded]))::before {
opacity: 1;
}
.media-default-skin:fullscreen media-poster ::slotted(img),
.media-default-skin:fullscreen media-poster img,
.media-default-skin:fullscreen > img {
object-fit: contain;
}
/* ==========================================================================
Media thumbnail
========================================================================== */
.media-default-skin .media-thumbnail {
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: 0.75rem;
& .media-thumbnail__image {
position: relative;
display: block;
overflow: clip;
border-radius: inherit;
&::after {
position: absolute;
inset: 0;
content: "";
background-image: linear-gradient(to top, oklch(0 0 0 / 0.8), oklch(0 0 0 / 0.3), oklch(0 0 0 / 0));
border-radius: inherit;
}
}
& .media-thumbnail__time {
position: absolute;
inset-inline: 0;
bottom: 0.5rem;
text-align: center;
}
& .media-thumbnail__spinner {
position: absolute;
top: 50%;
left: 50%;
opacity: 0;
translate: -50% -50%;
}
& .media-thumbnail__image,
& .media-thumbnail__spinner {
transition: opacity 150ms ease-out;
}
&:not(:has(.media-thumbnail__image[data-loading])) {
& .media-thumbnail__spinner {
--media-spinner-animation: none;
}
}
&:has(.media-thumbnail__image[data-loading]) {
& .media-thumbnail__image {
opacity: 0;
}
& .media-thumbnail__spinner {
opacity: 1;
}
}
}
/* ==========================================================================
Slider
========================================================================== */
.media-default-skin .media-slider {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: calc(infinity * 1px);
&[data-orientation="horizontal"] {
width: 100%;
min-width: 5rem;
height: 2rem;
}
&[data-orientation="vertical"] {
width: 2rem;
height: 5rem;
}
}
/* Track */
.media-default-skin .media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
border-radius: inherit;
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: 0.25rem;
}
&[data-orientation="vertical"] {
width: 0.25rem;
height: 100%;
}
}
/* Thumb */
.media-default-skin .media-slider__thumb {
position: absolute;
z-index: 10;
width: 0.625rem;
height: 0.625rem;
user-select: none;
outline: 4px solid transparent;
outline-offset: -4px;
background-color: currentColor;
border-radius: calc(infinity * 1px);
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.1)),
0 1px 3px 0 oklch(0 0 0 / 0.35),
0 1px 2px -1px oklch(0 0 0 / 0.35);
opacity: 0;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, height, width, outline-offset;
&[data-orientation="horizontal"] {
top: 50%;
left: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-fill));
left: 50%;
}
&:hover,
&:focus {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
}
.media-default-skin .media-slider:active .media-slider__thumb,
.media-default-skin .media-slider__thumb--persistent {
width: 0.75rem;
height: 0.75rem;
}
.media-default-skin .media-slider:hover .media-slider__thumb,
.media-default-skin .media-slider__thumb:focus-visible,
.media-default-skin .media-slider__thumb--persistent {
opacity: 1;
}
/* Preview */
.media-default-skin .media-slider__preview {
& .media-slider__value,
&::before {
opacity: 0;
scale: 0.5;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
& .media-slider__value {
position: absolute;
bottom: 2.25rem;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
filter: blur(8px);
translate: -50% 0.5rem;
transition-property: filter, opacity, scale, translate;
}
&::before {
display: block;
min-width: 0.25rem;
height: 0.25rem;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
}
&[data-pointing] .media-slider__value,
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
&[data-pointing] .media-slider__value {
filter: blur(0);
translate: -50% 0;
}
}
/* Shared track fills */
.media-default-skin .media-slider__buffer,
.media-default-skin .media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
}
.media-default-skin .media-slider__buffer[data-orientation="horizontal"],
.media-default-skin .media-slider__fill[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
}
.media-default-skin .media-slider__buffer[data-orientation="vertical"],
.media-default-skin .media-slider__fill[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
}
/* Buffer */
.media-default-skin .media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
transition-timing-function: ease-out;
transition-duration: 0.25s;
&[data-orientation="horizontal"] {
width: var(--media-slider-buffer);
transition-property: width;
}
&[data-orientation="vertical"] {
height: var(--media-slider-buffer);
transition-property: height;
}
}
/* Fill */
.media-default-skin .media-slider__fill {
background-color: currentColor;
&[data-orientation="horizontal"] {
width: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
height: var(--media-slider-fill);
}
}
/* Dragging — thumb and fill follow the pointer position */
.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
width: var(--media-slider-pointer);
}
.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
height: var(--media-slider-pointer);
}
/* ==========================================================================
Popups & Tooltips
========================================================================== */
.media-default-skin {
--media-popup-transition:
opacity var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
filter var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
transform var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
scale var(--media-popup-transition-timing-function) var(--media-popup-transition-duration);
}
.media-default-skin .media-popover,
.media-default-skin .media-tooltip {
--popup-translate-distance: 0.5rem;
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
filter: blur(4px);
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--popup-translate-x-distance, 0), var(--popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--popup-translate-y-distance: var(--popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--popup-translate-y-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--popup-translate-x-distance: var(--popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--popup-translate-x-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: left;
}
/* Safe area between trigger and popup */
&::before {
position: absolute;
pointer-events: inherit;
content: "";
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
inset-inline: 0;
width: 100%;
}
&[data-side="top"]::before {
top: 100%;
}
&[data-side="bottom"]::before {
bottom: 100%;
}
&[data-side="left"]::before,
&[data-side="right"]::before {
inset-block: 0;
height: 100%;
}
&[data-side="left"]::before {
left: 100%;
}
&[data-side="right"]::before {
right: 100%;
}
}
.media-default-skin .media-popover {
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-popover-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-popover-side-offset);
}
}
.media-default-skin .media-popover--volume {
padding: 0.75rem 0;
border-radius: calc(infinity * 1px);
&:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
white-space: nowrap;
border-radius: calc(infinity * 1px);
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
column-gap: 0.25rem;
align-items: center;
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-tooltip-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-tooltip-side-offset);
}
& .media-tooltip__kbd {
min-width: 1.5em;
padding: 0.1em;
font-family: inherit;
font-size: 90%;
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: 0.25rem;
}
}
/* ==========================================================================
Menus
Note: Menus use `.media-popover` styles for positioning and transitions.
========================================================================== */
.media-default-skin .media-menu {
--menu-transition-duration: 250ms;
--menu-max-height: 14rem;
--menu-padding: 0.25rem;
--menu-border-radius: 0.75rem;
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--menu-transition-duration: 0ms;
}
& .media-menu__panel {
position: absolute;
inset: 0;
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-in-out;
transition-duration: var(--menu-transition-duration);
transition-property: translate, filter;
will-change: translate, filter;
&[data-starting-style],
&[data-ending-style] {
overflow: hidden;
}
/* Root settings view — slides out when a submenu is active */
&[data-menu-root-view][data-menu-view-state="inactive"] {
filter: blur(8px);
translate: -100% 0;
/* Hide the indicator while transitioning */
& .media-menu__group::before {
display: none;
}
}
/* Submenu panels — slide in/out alongside the root view */
&[data-submenu] {
z-index: 10;
&:not([data-open], [data-ending-style]) {
translate: -100% 0;
transition-property: none;
}
&[data-starting-style],
&[data-ending-style] {
pointer-events: none;
filter: blur(8px);
}
&[data-starting-style][data-direction="forward"],
&[data-ending-style][data-direction="back"] {
translate: 100% 0;
}
&[data-ending-style][data-direction="forward"],
&[data-starting-style][data-direction="back"] {
translate: -100% 0;
}
}
}
& .media-menu__separator {
margin-block: 0.25rem;
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
& .media-menu__group {
display: flex;
flex-direction: column;
gap: 0.125rem;
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --media-menu-item-highlight-anchor;
inset: anchor(inside);
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.1);
border-radius: var(--menu-item-border-radius);
transition: inset ease-in-out 100ms;
}
}
}
& .media-menu__item,
& .media-menu__back {
display: flex;
gap: 0.375rem;
align-items: center;
padding: 0.375rem 0.5rem;
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-out;
transition-duration: 100ms;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
@supports (top: anchor(top)) {
anchor-name: --media-menu-item-highlight-anchor;
background-color: transparent;
}
}
& .media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__indicator {
flex-shrink: 0;
margin-right: -0.25rem;
margin-left: auto;
opacity: 0;
& .media-icon {
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__item {
justify-content: space-between;
font-variant-numeric: tabular-nums;
color: inherit;
&[aria-disabled="true"] {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
&[aria-checked="true"] .media-menu__indicator {
opacity: 1;
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
& .media-menu__tier {
padding-top: 1px;
padding-left: 0.125rem;
font-size: 0.5625rem;
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
& .media-menu__back {
width: 100%;
margin-bottom: 0.125rem;
}
& .media-menu__hint {
display: inline-flex;
gap: 0.25rem;
align-items: center;
min-width: 0;
padding-left: 0.5rem;
margin-left: auto;
font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
max-width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
width: 0.875rem;
height: 0.875rem;
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: 12rem;
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
transition:
var(--media-popup-transition),
height var(--media-popup-transition-timing-function) var(--menu-transition-duration),
width var(--media-popup-transition-timing-function) var(--menu-transition-duration);
/* Don't transition width and height on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
/* ==========================================================================
Native Caption Track
========================================================================== */
.media-default-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: -0.5rem;
&:has(.media-controls[data-visible]) {
--media-caption-track-y: -5.5rem;
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
--media-caption-track-y: -3.5rem;
}
}
}
.media-default-skin video::-webkit-media-text-track-container {
z-index: 1;
font-family: inherit;
scale: 0.98;
translate: 0 var(--media-caption-track-y);
transition: translate var(--media-caption-track-duration) ease-out;
transition-delay: var(--media-caption-track-delay);
}
/* ==========================================================================
Input Feedback
========================================================================== */
.media-default-skin .media-input-feedback {
position: absolute;
inset-inline: 0;
top: 0;
bottom: 3.5rem; /* Shift up a little in smaller containers */
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
color: var(--media-color-primary, oklch(1 0 0));
pointer-events: none;
@container media-root (width > 24rem) {
bottom: 0;
}
}
/* --- Feedback islands ------------------------------------------------------- */
.media-default-skin .media-input-feedback-island {
--media-surface-background-color: oklch(0 0 0 / 0.25);
position: absolute;
top: 0.75rem;
font-weight: 500;
color: inherit;
pointer-events: none;
border-radius: calc(Infinity * 1px);
transform-origin: top center;
transition-timing-function: ease-out;
transition-duration: 100ms;
.media-input-feedback-island__content {
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0.25rem 0.625rem;
/* Increase contrast of the content */
* {
mix-blend-mode: difference;
}
}
.media-icon {
display: none;
flex-shrink: 0;
}
.media-input-feedback-island__value {
margin-left: auto;
}
@media (pointer: coarse) {
transition-property: scale, translate, opacity;
will-change: scale, translate, opacity;
}
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
transition-property: scale, translate, filter, opacity;
will-change: scale, translate, filter, opacity;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: oklch(0 0 0);
}
/* Default hidden state */
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
transition-timing-function: ease-in;
transition-duration: 250ms;
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
scale: 0.9;
}
@media (prefers-reduced-motion: no-preference) {
&[data-ending-style] {
translate: 0 -25%;
}
}
}
}
.media-default-skin .media-input-feedback-island--volume {
width: min(80%, 12rem);
.media-input-feedback-island__content {
--media-progress-fill: var(--media-volume-fill);
background-image: linear-gradient(
to right,
currentColor 0%,
currentColor var(--media-progress-fill),
transparent var(--media-progress-fill),
transparent 100%
);
border-radius: inherit;
transition: --media-progress-fill 200ms linear;
}
&[data-level="high"] .media-icon--volume-high,
&[data-level="low"] .media-icon--volume-low,
&[data-level="off"] .media-icon--volume-off {
display: block;
}
@media (prefers-reduced-motion: no-preference) {
&[data-min],
&[data-max] {
animation: media-shake 300ms ease-in-out;
}
}
}
.media-default-skin .media-input-feedback-island--status {
&[data-status="captions-on"] .media-icon--captions-on,
&[data-status="captions-off"] .media-icon--captions-off,
&[data-status="fullscreen"] .media-icon--fullscreen-enter,
&[data-status="exit-fullscreen"] .media-icon--fullscreen-exit,
&[data-status="pip"] .media-icon--pip-enter,
&[data-status="exit-pip"] .media-icon--pip-exit {
display: block;
}
}
/* --- Bubble ---------------------------------------------------------------- */
.media-default-skin .media-input-feedback-bubble {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: 1rem;
text-align: center;
/* Central bubble (play, pause) */
&:not([data-direction]) {
background: oklch(0 0 0 / 0.35);
border-radius: 100%;
backdrop-filter: blur(8px);
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
scale: 0.85;
}
&[data-ending-style] {
transition-timing-function: ease-in;
transition-duration: 100ms;
}
@media (prefers-reduced-motion: reduce) {
transition-duration: 50ms;
transition-property: opacity;
}
}
/* Directional bubbles (seek) */
&[data-direction] {
gap: 0.25rem;
@container media-root (width > 24rem) {
padding: 1.5rem;
}
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
/* Icons */
.media-icon {
display: none;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
&.media-icon--play {
translate: 1px 0;
}
}
&[data-direction] .media-icon--seek,
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
display: block;
}
&[data-direction="backward"] .media-icon--seek {
scale: -1 1;
}
@media (prefers-reduced-motion: no-preference) {
.media-icon--seek {
transition-timing-function: ease-in-out;
transition-duration: 200ms;
transition-property: translate, opacity;
}
&[data-starting-style] .media-icon--seek,
&[data-ending-style] .media-icon--seek {
opacity: 0;
}
&[data-direction="forward"][data-starting-style] .media-icon--seek {
translate: -60% 0;
}
&[data-direction="backward"][data-starting-style] .media-icon--seek {
translate: 60% 0;
}
}
}
/* ==========================================================================
Icon State Visibility for Video Skins
Data-attribute-driven visibility rules for multi-state icon buttons.
Uses :is() with both element selectors (for HTML custom element wrappers)
and class selectors (for React rendered SVG elements).
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon--restart,
.media-button--play .media-icon--play,
.media-button--play .media-icon--pause,
.media-button--mute .media-icon--volume-off,
.media-button--mute .media-icon--volume-low,
.media-button--mute .media-icon--volume-high,
.media-button--fullscreen .media-icon--fullscreen-enter,
.media-button--fullscreen .media-icon--fullscreen-exit,
.media-button--pip .media-icon--pip-enter,
.media-button--pip .media-icon--pip-exit,
.media-button--cast .media-icon--cast-enter,
.media-button--cast .media-icon--cast-exit,
.media-button--airplay .media-icon--airplay-enter,
.media-button--airplay .media-icon--airplay-exit,
.media-button--captions .media-icon--captions-off,
.media-button--captions .media-icon--captions-on {
display: none;
opacity: 0;
}
/* --- Active icon per state --- */
/* Play: ended → restart */
.media-button--play[data-ended] .media-icon--restart,
/* Play: paused or not yet started (not ended) → play */
.media-button--play:not([data-ended])[data-paused] .media-icon--play,
.media-button--play:not([data-ended]):not([data-started]) .media-icon--play,
/* Play: started and not paused/ended → pause */
.media-button--play[data-started]:not([data-paused]):not([data-ended]) .media-icon--pause,
/* Mute: muted → volume off */
.media-button--mute[data-muted] .media-icon--volume-off,
/* Mute: volume low (not muted) → volume low */
.media-button--mute:not([data-muted])[data-volume-level="low"] .media-icon--volume-low,
/* Mute: volume high (not muted, not low) → volume high */
.media-button--mute:not([data-muted]):not([data-volume-level="low"]) .media-icon--volume-high,
/* Fullscreen: not fullscreen → enter */
.media-button--fullscreen:not([data-fullscreen]) .media-icon--fullscreen-enter,
/* Fullscreen: fullscreen → exit */
.media-button--fullscreen[data-fullscreen] .media-icon--fullscreen-exit,
/* Picture-in-Picture: not active → enter */
.media-button--pip:not([data-pip]) .media-icon--pip-enter,
/* Picture-in-Picture: active → exit */
.media-button--pip[data-pip] .media-icon--pip-exit,
/* Cast: not connected → enter */
.media-button--cast:not([data-cast-state="connected"]) .media-icon--cast-enter,
/* Cast: connected → exit */
.media-button--cast[data-cast-state="connected"] .media-icon--cast-exit,
/* AirPlay: not connected → enter */
.media-button--airplay:not([data-airplay-state="connected"]) .media-icon--airplay-enter,
/* AirPlay: connected → exit */
.media-button--airplay[data-airplay-state="connected"] .media-icon--airplay-exit,
/* Captions: not active → captions off */
.media-button--captions:not([data-active]) .media-icon--captions-off,
/* Captions: active → captions on */
.media-button--captions[data-active] .media-icon--captions-on {
display: block;
opacity: 1;
}
/* --- Pause keyframe animations on inactive icons --- */
/* The airplay-exit SVG defines its keyframes against CSS variables (mirroring
the spinner pattern). When the AirPlay session isn't active the SVG is
still in the DOM — just `display: none` — so its animations would keep
running. Set the variables to `none` to short-circuit the keyframes. */
.media-button--airplay:not([data-airplay-state="connected"]) {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
/* -------------------------------------------------------------------------- */
/* Global @keyframes for all video skins (CSS & Tailwind) */
/* -------------------------------------------------------------------------- */
@keyframes media-shake {
0%,
100% {
translate: 0 0;
}
20% {
translate: -6px 0;
}
40% {
translate: 4px 0;
}
60% {
translate: -2px 0;
}
80% {
translate: 1px 0;
}
}
/* -------------------------------------------------------------------------- */
/* Global @properties for all video skins (CSS & Tailwind) */
/* -------------------------------------------------------------------------- */
@property --media-progress-fill {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
/* ==========================================================================
Root
========================================================================== */
.media-default-skin--video {
--media-spring-timing-function: linear(
0,
0.034 1.5%,
0.763 9.7%,
1.066 13.9%,
1.198 19.9%,
1.184 21.8%,
0.963 37.5%,
0.997 50.9%,
1
);
--media-border-color: oklch(0 0 0 / 0.1);
--media-surface-background-color: oklch(1 0 0 / 0.1);
--media-surface-inner-border-color: oklch(1 0 0 / 0.05);
--media-surface-outer-border-color: oklch(0 0 0 / 0.1);
--media-surface-shadow-color: oklch(0 0 0 / 0.15);
--media-surface-backdrop-filter: blur(16px) saturate(1.5);
--media-video-border-radius: var(--media-border-radius, 2rem);
--media-controls-transition-duration: 100ms;
--media-controls-transition-timing-function: ease-out;
--media-error-dialog-transition-duration: 350ms;
--media-error-dialog-transition-delay: 100ms;
--media-error-dialog-transition-timing-function: var(--media-spring-timing-function);
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
--media-tooltip-side-offset: 0.75rem;
--media-tooltip-boundary-offset: 0.5rem;
--media-popover-side-offset: 0.5rem;
--media-popover-boundary-offset: 0.5rem;
overflow: clip;
background: oklch(0 0 0);
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-error-dialog-transition-timing-function: ease-out;
--media-popup-transition-duration: 0ms;
}
@media (prefers-color-scheme: dark) {
--media-border-color: oklch(1 0 0 / 0.15);
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: oklch(0 0 0);
--media-surface-inner-border-color: oklch(1 0 0 / 0.25);
--media-surface-outer-border-color: transparent;
}
&:has(.media-controls:not([data-visible])) {
/* Slight delay to hide controls on non-touch devices after interaction */
@media (pointer: fine) {
--media-controls-transition-duration: 300ms;
}
@media (pointer: coarse) {
--media-controls-transition-duration: 150ms;
}
@media (prefers-reduced-motion: reduce) {
--media-controls-transition-duration: 50ms;
}
}
/* Inner border ring */
&::after {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
content: "";
border-radius: inherit;
box-shadow: inset 0 0 0 1px var(--media-border-color);
}
&:fullscreen {
--media-border-radius: 0;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-default-skin--video .media-error {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
}
.media-default-skin--video .media-error__dialog {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 18rem;
padding: 0.75rem;
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.25);
border-radius: 1.75rem;
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: var(--media-error-dialog-transition-timing-function);
transition-duration: var(--media-error-dialog-transition-duration);
transition-property: opacity, scale;
}
.media-default-skin--video .media-error[data-starting-style] .media-error__dialog,
.media-default-skin--video .media-error[data-ending-style] .media-error__dialog {
opacity: 0;
scale: 0.5;
}
.media-default-skin--video .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
.media-default-skin--video .media-error__content {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem 0.5rem 0.375rem;
text-shadow: inherit;
}
.media-default-skin--video .media-error__title {
font-size: 1rem;
}
/* ==========================================================================
Controls (hide/show behavior)
========================================================================== */
.media-default-skin--video .media-controls {
position: absolute;
inset-inline: 0.5rem;
bottom: 0.5rem;
z-index: 10;
flex-wrap: wrap;
color: var(--media-color-primary, oklch(1 0 0));
transform-origin: bottom;
transition-timing-function: var(--media-controls-transition-timing-function);
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) {
transition-property: scale, filter, opacity;
will-change: scale, filter, opacity;
}
@media (pointer: coarse) {
transition-property: scale, opacity;
will-change: scale, opacity;
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
scale: 0.95;
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
}
& .media-time-controls {
flex: 0 0 100%;
order: -1;
padding-inline: 0.625rem;
}
& .media-button-group:first-child {
flex: 1;
text-align: left;
}
& .media-button-group:last-child {
flex: 1;
justify-content: end;
}
@container media-root (width > 42rem) {
inset-inline: 0.75rem;
bottom: 0.75rem;
flex-wrap: nowrap;
column-gap: 0.125rem;
padding: 0.25rem;
& .media-time-controls {
flex: 1;
order: unset;
}
& .media-button-group:first-child,
& .media-button-group:last-child {
flex: 0 0 auto;
}
}
}
.media-default-skin--video .media-error[data-open] ~ .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-default-skin--video:has(.media-controls:not([data-visible])) {
cursor: none;
}
/* ==========================================================================
Sliders
========================================================================== */
.media-default-skin--video .media-slider__track {
background-color: oklch(1 0 0 / 0.2);
box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);
}
.media-default-skin--video .media-slider__thumbnail {
--media-slider-thumbnail-max-width: 11rem;
--media-slider-thumbnail-max-height: 8rem;
--media-slider-thumbnail-padding: -1.125rem;
/**
Inset is the difference between the container width and the slider (100%) width.
Divided by 2 as we render the time on both sides.
*/
--media-slider-thumbnail-inset: calc((100cqi - 100%) / 2);
position: absolute;
bottom: calc(100% + 1.2rem);
left: clamp(
calc(
var(--media-slider-thumbnail-max-width) /
2 +
var(--media-slider-thumbnail-padding) -
var(--media-slider-thumbnail-inset)
),
var(--media-slider-pointer),
calc(
100% -
var(--media-slider-thumbnail-max-width) /
2 -
var(--media-slider-thumbnail-padding) +
var(--media-slider-thumbnail-inset)
)
);
pointer-events: none;
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: scale, opacity, filter;
& .media-thumbnail__image {
max-width: var(--media-slider-thumbnail-max-width);
max-height: var(--media-slider-thumbnail-max-height);
}
}
.media-default-skin--video .media-slider[data-pointing] .media-slider__thumbnail:has([role="img"]:not([data-hidden])) {
opacity: 1;
filter: blur(0);
scale: 1;
}
.media-default-skin--video
.media-slider__thumbnail:has([role="img"]:not([data-hidden]))
+ .media-slider__preview
.media-slider__value {
display: none;
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/video-ui.js"></script>
<link rel="stylesheet" href="./player.css">
<video-player>
<media-container class="**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,2rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 scrollbar-thin scrollbar-thumb-current/30 [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-auto [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-thumb-current/80 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] bg-black overflow-clip after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10 after:inset-0 after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 [&_::slotted(video)]:block [&_::slotted(video)]:w-full [&_::slotted(video)]:h-full [&_::slotted(video)]:rounded-(--media-video-border-radius) [&_::slotted(video)]:[object-fit:var(--media-object-fit,contain)] [&_::slotted(video)]:[object-position:var(--media-object-position,center)] [--media-spring-timing-function:linear(0,0.034_1.5%,0.763_9.7%,1.066_13.9%,1.198_19.9%,1.184_21.8%,0.963_37.5%,0.997_50.9%,1)] [--media-video-border-radius:var(--media-border-radius,2rem)] [--media-controls-transition-duration:100ms] [--media-controls-transition-timing-function:ease-out] [--media-error-dialog-transition-duration:350ms] [--media-error-dialog-transition-delay:100ms] [--media-error-dialog-transition-timing-function:var(--media-spring-timing-function)] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-surface-background-color:oklch(1_0_0/0.1)] [--media-surface-inner-border-color:oklch(1_0_0/0.05)] [--media-surface-outer-border-color:oklch(0_0_0/0.1)] [--media-surface-shadow-color:oklch(0_0_0/0.15)] [--media-surface-backdrop-filter:blur(16px)_saturate(1.5)] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-error-dialog-transition-timing-function:ease-out] [--media-tooltip-side-offset:0.75rem] [--media-tooltip-boundary-offset:0.5rem] [--media-popover-side-offset:0.5rem] [--media-popover-boundary-offset:0.5rem] motion-reduce:[--media-popup-transition-duration:0ms] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] contrast-more:[--media-surface-background-color:oklch(0_0_0)] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-inner-border-color:oklch(1_0_0/0.25)] contrast-more:[--media-surface-inner-border-color:oklch(1_0_0/0.25)] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:transparent] contrast-more:[--media-surface-outer-border-color:transparent] pointer-fine:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:300ms] pointer-coarse:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:150ms] motion-reduce:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:50ms] [--media-caption-track-y:-0.5rem] [--media-caption-track-delay:25ms] [--media-caption-track-duration:var(--media-controls-transition-duration)] has-[[data-controls][data-visible]]:[--media-caption-track-y:-5.5rem] @2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:-3.5rem] [&:fullscreen]:[--media-border-radius:0] [&:fullscreen_::slotted(video)]:object-contain">
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" playsinline></video>
<media-poster class="absolute inset-0 w-full h-full pointer-events-none transition-opacity duration-250 not-data-visible:opacity-0 before:absolute before:inset-0 before:pointer-events-none before:[background-image:var(--media-poster-placeholder,none)] before:bg-no-repeat before:[background-position:var(--media-object-position,center)] before:[background-size:var(--media-object-fit,contain)] before:[filter:blur(var(--media-poster-placeholder-blur,20px))] [&_::slotted(img)]:absolute [&_::slotted(img)]:inset-0 [&_::slotted(img)]:w-full [&_::slotted(img)]:h-full [&_::slotted(img)]:[object-fit:var(--media-object-fit,contain)] [&_::slotted(img)]:[object-position:var(--media-object-position,center)] [&_::slotted(img)]:rounded-(--media-video-border-radius)">
<img src="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp" />
</media-poster>
<media-buffering-indicator class="peer/buffering absolute inset-0 z-10 hidden place-content-center pointer-events-none text-white not-data-visible:[--media-spinner-animation:none] data-visible:grid">
<media-icon name="spinner" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
</media-buffering-indicator>
<media-error-dialog class="peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none">
<div class="flex flex-col gap-3 max-w-72 p-3 rounded-[1.75rem] text-white transition-[opacity,scale,transform] duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) ease-(--media-error-dialog-transition-timing-function) group-data-starting-style/error:opacity-0 group-data-starting-style/error:scale-50 group-data-ending-style/error:opacity-0 group-data-ending-style/error:scale-50 group-data-ending-style/error:delay-0 bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 text-shadow-2xs text-shadow-black/25">
<div class="flex flex-col gap-2 px-2 pt-2 pb-1.5 text-shadow-inherit">
<media-alert-dialog-title class="font-semibold leading-tight text-base">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="opacity-70 wrap-anywhere"></media-alert-dialog-description>
</div>
<div class="flex gap-2 *:flex-1">
<media-alert-dialog-close class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-white text-black font-medium text-shadow-none">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<media-controls data-controls="" class="peer/controls @container/media-controls p-[0.375rem] flex items-center gap-x-[0.075rem] rounded-3xl text-shadow-2xs text-shadow-(color:--media-current-shadow-color) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 absolute bottom-2 inset-x-2 flex-wrap [color:var(--media-color-primary,oklch(1_0_0))] z-10 peer-data-open/error:hidden ease-(--media-controls-transition-timing-function) origin-bottom duration-(--media-controls-transition-duration) pointer-fine:will-change-[scale,filter,opacity] pointer-fine:transition-[scale,filter,opacity] pointer-coarse:will-change-[scale,opacity] pointer-coarse:transition-[scale,opacity] not-data-visible:pointer-events-none not-data-visible:opacity-0 motion-safe:not-data-visible:scale-90 pointer-fine:motion-safe:not-data-visible:blur-sm @2xl/media-root:bottom-3 @2xl/media-root:inset-x-3 @2xl/media-root:flex-nowrap @2xl/media-root:gap-x-0.5 @2xl/media-root:p-1">
<media-tooltip-group>
<div class="flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 @2xl/media-root:flex-none">
<media-play-button commandfor="play-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 group">
<media-icon name="restart" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-ended:block group-data-ended:opacity-100"></media-icon>
<media-icon name="play" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100"></media-icon>
<media-icon name="pause" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100"></media-icon>
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-backward-tooltip" seconds="-10" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90">
<span class="relative grid">
<media-icon name="seek" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out [scale:-1_1]"></media-icon>
<span class="text-[10px] font-medium tracking-tighter tabular-nums absolute -left-px -bottom-0.75">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-backward-tooltip" side="top" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-forward-tooltip" seconds="10" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90">
<span class="relative grid">
<media-icon name="seek" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
<span class="text-[10px] font-medium tracking-tighter tabular-nums absolute -right-px -bottom-0.75">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-forward-tooltip" side="top" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
</div>
<div class="@container/media-time flex items-center flex-1 gap-3 px-2 grow-0 shrink-0 basis-full order-[-1] px-2.5 @2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]">
<media-time type="current" class="hidden @2xs/media-time:block tabular-nums"></media-time>
<media-time-slider class="group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20">
<media-slider-track class="relative isolate overflow-hidden rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1 data-[orientation=vertical]:h-full bg-white/20 ring-1 ring-black/5">
<media-slider-fill class="absolute rounded-[inherit] pointer-events-none bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)"></media-slider-fill>
<media-slider-buffer class="absolute rounded-[inherit] pointer-events-none bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)"></media-slider-buffer>
</media-slider-track>
<media-slider-thumb class="z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.35),0_1px_2px_-1px_oklch(0_0_0/0.35)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/15 hover:outline-offset-0 focus-visible:outline-current/15 focus-visible:outline-offset-0 after:absolute after:-inset-1 after:rounded-[inherit] after:shadow-[0_0_0_2px_currentColor] after:pointer-events-none after:transition-[opacity,scale] after:duration-150 after:ease-out after:opacity-0 after:scale-50 focus-visible:after:opacity-100 focus-visible:after:scale-100 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))] size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3"></media-slider-thumb>
<div class="group/thumbnail peer/thumbnail pointer-events-none bg-black/90 rounded-xl bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-slider-thumbnail-max-width:11rem] [--media-slider-thumbnail-max-height:8rem] [--media-slider-thumbnail-padding:-1.125rem] [--media-slider-thumbnail-inset:calc((100cqi-100%)/2)] absolute [left:clamp(calc(var(--media-slider-thumbnail-max-width)/2+var(--media-slider-thumbnail-padding)-var(--media-slider-thumbnail-inset)),var(--media-slider-pointer),calc(100%-var(--media-slider-thumbnail-max-width)/2-var(--media-slider-thumbnail-padding)+var(--media-slider-thumbnail-inset)))] bottom-[calc(100%+1.2rem)] -translate-x-1/2 opacity-0 scale-80 blur-sm origin-bottom transition-[scale,opacity,filter] duration-150 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:opacity-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:scale-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:blur-none">
<media-slider-thumbnail class="block relative overflow-clip rounded-[inherit] transition-opacity duration-150 ease-out after:absolute after:inset-0 after:rounded-[inherit] after:bg-linear-to-t after:from-black/80 after:via-black/30 after:to-black/0 data-loading:opacity-0 max-w-(--media-slider-thumbnail-max-width) max-h-(--media-slider-thumbnail-max-height)"></media-slider-thumbnail>
<media-slider-value type="pointer" class="hidden @2xs/media-time:block tabular-nums absolute bottom-2 inset-x-0 text-center tabular-nums"></media-slider-value>
<media-icon name="spinner" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity duration-150 ease-out group-not-has-[[role=img][data-loading]]/thumbnail:[--media-spinner-animation:none] group-has-[[role=img][data-loading]]/thumbnail:opacity-100"></media-icon>
</div>
<media-slider-preview class="group/preview before:block before:min-w-1 before:h-1 before:bg-current before:rounded-full before:opacity-0 before:scale-50 before:shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_2px_0_oklch(0_0_0/0.35)] before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden">
<media-slider-value type="pointer" class="absolute bottom-9 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none hidden @2xs/media-time:block tabular-nums"></media-slider-value>
</media-slider-preview>
</media-time-slider>
<media-time toggle type="remaining" class="tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2"></media-time>
</div>
<div class="flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 justify-end @2xl/media-root:flex-none group/settings">
<media-mute-button commandfor="video-volume-popover" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 group">
<media-icon name="volume-off" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-muted:block group-data-muted:opacity-100"></media-icon>
<media-icon name="volume-low" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100"></media-icon>
<media-icon name="volume-high" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100"></media-icon>
</media-mute-button>
<media-popover id="video-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) py-3 px-0 rounded-full">
<media-volume-slider class="group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="relative isolate overflow-hidden rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1 data-[orientation=vertical]:h-full bg-white/20 ring-1 ring-black/5">
<media-slider-fill class="absolute rounded-[inherit] pointer-events-none bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.35),0_1px_2px_-1px_oklch(0_0_0/0.35)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/15 hover:outline-offset-0 focus-visible:outline-current/15 focus-visible:outline-offset-0 after:absolute after:-inset-1 after:rounded-[inherit] after:shadow-[0_0_0_2px_currentColor] after:pointer-events-none after:transition-[opacity,scale] after:duration-150 after:ease-out after:opacity-0 after:scale-50 focus-visible:after:opacity-100 focus-visible:after:scale-100 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))] size-3"></media-slider-thumb>
</media-volume-slider>
</media-popover>
<button commandfor="settings-menu" aria-label="Settings" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 group hidden group-has-[[data-availability=available]]/settings:grid media-button--settings">
<media-icon name="gear" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0"></media-icon>
</button>
<media-menu id="settings-menu" side="top" align="center" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.25rem] [--menu-border-radius:0.75rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none [--media-popup-transition:var(--media-popup-base-transition),height_var(--media-popup-transition-timing-function)_var(--menu-transition-duration),width_var(--media-popup-transition-timing-function)_var(--menu-transition-duration)] data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)] data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)] min-w-48 w-(--media-menu-width) h-(--media-menu-height) !overflow-hidden [--media-popover-side-offset:0.5rem] [--media-popover-boundary-offset:0.5rem]">
<media-menu-view class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] group/menu-root-view data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur">
<div class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<media-menu-item commandfor="settings-quality-menu" type="quality" data-setting="quality" class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50 media-menu__item--submenu">
<media-icon name="switches" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
<span>Quality</span>
<span class="ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65">
<media-menu-item-value class="max-w-24 overflow-hidden text-ellipsis whitespace-nowrap"></media-menu-item-value>
<media-icon name="chevron" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-audio-menu" type="audio-track" data-setting="audio-track" class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50 media-menu__item--submenu">
<media-icon name="speech" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
<span>Audio</span>
<span class="ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65">
<media-menu-item-value class="max-w-24 overflow-hidden text-ellipsis whitespace-nowrap"></media-menu-item-value>
<media-icon name="chevron" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out size-3.5"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-speed-menu" type="playback-rate" data-setting="playback-rate" class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50 media-menu__item--submenu">
<media-icon name="speed" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
<span>Speed</span>
<span class="ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65">
<media-menu-item-value class="max-w-24 overflow-hidden text-ellipsis whitespace-nowrap"></media-menu-item-value>
<media-icon name="chevron" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-captions-menu" type="captions" data-setting="captions" class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50 media-menu__item--submenu">
<media-icon name="captions-off" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
<span>Captions</span>
<span class="ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65">
<media-menu-item-value class="max-w-24 overflow-hidden text-ellipsis whitespace-nowrap"></media-menu-item-value>
<media-icon name="chevron" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5"></media-icon>
</span>
</media-menu-item>
</div>
</media-menu-view>
<media-menu id="settings-quality-menu" class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full">
<media-menu-back class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full">
<media-icon name="chevron" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5 [scale:-1_1]"></media-icon>
Quality
</media-menu-back>
<div class="my-1 border-b border-[oklch(0_0_0/0.1)] shadow-[0_1px_0_0_oklch(1_0_0/0.075)]"></div>
<media-quality-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span>
<span data-part="label"></span>
<sup data-part="tier" class="pl-0.5 pt-px text-[0.5625rem] font-semibold leading-none text-current/70"></sup>
</span>
<span data-part="badge" class="rounded-full bg-current/10 px-1.5 py-0.5 text-[0.6875rem] font-medium leading-none text-current/70"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-quality-radio-group>
</media-menu>
<media-menu id="settings-audio-menu" class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full">
<media-menu-back class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full">
<media-icon name="chevron" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out size-3.5 [scale:-1_1]"></media-icon>
Audio
</media-menu-back>
<div class="my-1 border-b border-[oklch(0_0_0/0.1)] shadow-[0_1px_0_0_oklch(1_0_0/0.075)]"></div>
<media-audio-track-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-audio-track-radio-group>
</media-menu>
<media-menu id="settings-speed-menu" class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full">
<media-menu-back class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full">
<media-icon name="chevron" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5 [scale:-1_1]"></media-icon>
Speed
</media-menu-back>
<div class="my-1 border-b border-[oklch(0_0_0/0.1)] shadow-[0_1px_0_0_oklch(1_0_0/0.075)]"></div>
<media-playback-rate-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
</media-menu>
<media-menu id="settings-captions-menu" class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full">
<media-menu-back class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full">
<media-icon name="chevron" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5 [scale:-1_1]"></media-icon>
Captions
</media-menu-back>
<div class="my-1 border-b border-[oklch(0_0_0/0.1)] shadow-[0_1px_0_0_oklch(1_0_0/0.075)]"></div>
<media-captions-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-captions-radio-group>
</media-menu>
</media-menu>
<media-cast-button commandfor="cast-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 group">
<media-icon name="cast-enter" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100"></media-icon>
<media-icon name="cast-exit" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100"></media-icon>
</media-cast-button>
<media-tooltip id="cast-tooltip" side="top" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-airplay-button commandfor="airplay-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]">
<media-icon name="airplay-enter" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100"></media-icon>
<media-icon name="airplay-exit" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100"></media-icon>
</media-airplay-button>
<media-tooltip id="airplay-tooltip" side="top" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-pip-button commandfor="pip-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 group">
<media-icon name="pip-enter" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100"></media-icon>
<media-icon name="pip-exit" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-pip:block group-data-pip:opacity-100"></media-icon>
</media-pip-button>
<media-tooltip id="pip-tooltip" side="top" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-fullscreen-button commandfor="fullscreen-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 group">
<media-icon name="fullscreen-enter" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100"></media-icon>
<media-icon name="fullscreen-exit" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100"></media-icon>
</media-fullscreen-button>
<media-tooltip id="fullscreen-tooltip" side="top" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
</div>
</media-tooltip-group>
</media-controls>
<div class="absolute inset-0 flex flex-col items-start pointer-events-none rounded-[inherit] opacity-0 bg-linear-to-t from-black/50 via-black/30 via-25% to-transparent backdrop-blur-none backdrop-saturate-100 transition-[opacity,backdrop-filter] duration-(--media-controls-transition-duration) ease-out peer-data-visible/controls:opacity-100 peer-data-visible/buffering:bg-black/35 peer-data-visible/buffering:opacity-100 peer-data-visible/buffering:backdrop-blur-sm peer-data-open/error:opacity-100 peer-data-open/error:duration-(--media-error-dialog-transition-duration) peer-data-open/error:delay-(--media-error-dialog-transition-delay) peer-data-open/error:backdrop-blur-lg peer-data-open/error:backdrop-saturate-150"></div>
<!-- Hotkeys -->
<media-hotkey keys="Space" action="togglePaused"></media-hotkey>
<media-hotkey keys="k" action="togglePaused"></media-hotkey>
<media-hotkey keys="m" action="toggleMuted"></media-hotkey>
<media-hotkey keys="f" action="toggleFullscreen"></media-hotkey>
<media-hotkey keys="c" action="toggleSubtitles"></media-hotkey>
<media-hotkey keys="i" action="togglePictureInPicture"></media-hotkey>
<media-hotkey keys="ArrowRight" action="seekStep" value="5"></media-hotkey>
<media-hotkey keys="ArrowLeft" action="seekStep" value="-5"></media-hotkey>
<media-hotkey keys="l" action="seekStep" value="10"></media-hotkey>
<media-hotkey keys="j" action="seekStep" value="-10"></media-hotkey>
<media-hotkey keys="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
<media-hotkey keys="0-9" action="seekToPercent"></media-hotkey>
<media-hotkey keys="Home" action="seekToPercent" value="0"></media-hotkey>
<media-hotkey keys="End" action="seekToPercent" value="100"></media-hotkey>
<media-hotkey keys=">" action="speedUp"></media-hotkey>
<media-hotkey keys="<" action="speedDown"></media-hotkey>
<!-- Gestures -->
<media-gesture type="tap" action="togglePaused" pointer="mouse" region="center"></media-gesture>
<media-gesture type="tap" action="toggleControls" pointer="touch"></media-gesture>
<media-gesture type="doubletap" action="seekStep" value="-10" region="left"></media-gesture>
<media-gesture type="doubletap" action="toggleFullscreen" region="center"></media-gesture>
<media-gesture type="doubletap" action="seekStep" value="10" region="right"></media-gesture>
<!-- Input Feedback -->
<media-status-announcer></media-status-announcer>
<div class="absolute inset-x-0 top-0 bottom-14 pointer-events-none grid grid-cols-3 items-center justify-items-center @2xl/media-root:bottom-0 [color:var(--media-color-primary,oklch(1_0_0))]">
<media-volume-indicator
hidden
class="group/input-indicator [--media-surface-background-color:oklch(0_0_0/0.25)] absolute top-3 rounded-full origin-top pointer-events-none text-inherit font-medium duration-100 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-250 data-starting-style:ease-in data-ending-style:duration-250 data-ending-style:ease-in pointer-coarse:will-change-[scale,translate,opacity] pointer-coarse:transition-[scale,translate,opacity] pointer-fine:motion-safe:will-change-[scale,translate,filter,opacity] pointer-fine:motion-safe:transition-[scale,translate,filter,opacity] pointer-fine:motion-safe:data-starting-style:blur-sm pointer-fine:motion-safe:data-starting-style:scale-90 pointer-fine:motion-safe:data-ending-style:blur-sm pointer-fine:motion-safe:data-ending-style:scale-90 motion-safe:data-ending-style:-translate-y-1/4 [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] contrast-more:[--media-surface-background-color:oklch(0_0_0)] bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 w-[min(80%,12rem)] *:[--media-progress-fill:var(--media-volume-fill)] *:rounded-[inherit] *:[background-image:linear-gradient(to_right,currentColor_0%,currentColor_var(--media-progress-fill),transparent_var(--media-progress-fill),transparent_100%)] *:[transition:--media-progress-fill_200ms_linear] data-open:duration-100 data-min:animate-media-shake data-max:animate-media-shake motion-reduce:data-min:animate-none motion-reduce:data-max:animate-none"
>
<media-volume-indicator-fill class="flex justify-between items-center gap-2 px-2.5 py-1 w-full **:mix-blend-difference">
<media-icon name="volume-high" class="hidden shrink-0 group-data-[level=high]/input-indicator:block"></media-icon>
<media-icon name="volume-low" class="hidden shrink-0 group-data-[level=low]/input-indicator:block"></media-icon>
<media-icon name="volume-off" class="hidden shrink-0 group-data-[level=off]/input-indicator:block"></media-icon>
<media-volume-indicator-value class="ml-auto"></media-volume-indicator-value>
</media-volume-indicator-fill>
</media-volume-indicator>
<media-status-indicator hidden actions="toggleSubtitles toggleFullscreen togglePictureInPicture" class="group/input-indicator [--media-surface-background-color:oklch(0_0_0/0.25)] absolute top-3 rounded-full origin-top pointer-events-none text-inherit font-medium duration-100 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-250 data-starting-style:ease-in data-ending-style:duration-250 data-ending-style:ease-in pointer-coarse:will-change-[scale,translate,opacity] pointer-coarse:transition-[scale,translate,opacity] pointer-fine:motion-safe:will-change-[scale,translate,filter,opacity] pointer-fine:motion-safe:transition-[scale,translate,filter,opacity] pointer-fine:motion-safe:data-starting-style:blur-sm pointer-fine:motion-safe:data-starting-style:scale-90 pointer-fine:motion-safe:data-ending-style:blur-sm pointer-fine:motion-safe:data-ending-style:scale-90 motion-safe:data-ending-style:-translate-y-1/4 [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] contrast-more:[--media-surface-background-color:oklch(0_0_0)] bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 data-open:duration-100">
<div class="flex justify-between items-center gap-2 px-2.5 py-1 w-full **:mix-blend-difference">
<media-icon name="captions-on" class="hidden shrink-0 group-data-[status=captions-on]/input-indicator:block"></media-icon>
<media-icon name="captions-off" class="hidden shrink-0 group-data-[status=captions-off]/input-indicator:block"></media-icon>
<media-icon name="fullscreen-enter" class="hidden shrink-0 group-data-[status=fullscreen]/input-indicator:block"></media-icon>
<media-icon name="fullscreen-exit" class="hidden shrink-0 group-data-[status=exit-fullscreen]/input-indicator:block"></media-icon>
<media-icon name="pip-enter" class="hidden shrink-0 group-data-[status=pip]/input-indicator:block"></media-icon>
<media-icon name="pip-exit" class="hidden shrink-0 group-data-[status=exit-pip]/input-indicator:block"></media-icon>
<media-status-indicator-value class="ml-auto"></media-status-indicator-value>
</div>
</media-status-indicator>
<media-seek-indicator hidden class="group/input-indicator col-start-2 row-start-1 grid place-content-center text-center p-4 data-direction:gap-1 @2xl/media-root:data-direction:p-6 not-data-direction:bg-black/35 not-data-direction:rounded-full not-data-direction:backdrop-blur-sm not-data-direction:[transition-property:opacity,scale] not-data-direction:duration-200 not-data-direction:ease-out motion-reduce:not-data-direction:transition-opacity motion-reduce:not-data-direction:duration-50 not-data-direction:data-starting-style:opacity-0 not-data-direction:data-ending-style:opacity-0 not-data-direction:data-starting-style:scale-[0.85] not-data-direction:data-ending-style:scale-[0.85] not-data-direction:data-ending-style:duration-100 not-data-direction:data-ending-style:ease-in data-[direction=backward]:col-start-1 data-[direction=backward]:justify-self-start data-[direction=forward]:col-start-3 data-[direction=forward]:justify-self-end">
<media-icon name="chevron" class="hidden size-[calc(var(--media-icon-size)*1.5)] group-data-direction/input-indicator:block group-data-[direction=backward]/input-indicator:[scale:-1_1] motion-safe:transition-[translate,opacity] motion-safe:duration-200 motion-safe:ease-in-out motion-safe:group-data-starting-style/input-indicator:opacity-0 motion-safe:group-data-ending-style/input-indicator:opacity-0 motion-safe:group-data-[direction=forward]/input-indicator:group-data-starting-style/input-indicator:[translate:-60%_0] motion-safe:group-data-[direction=backward]/input-indicator:group-data-starting-style/input-indicator:[translate:60%_0]"></media-icon>
<media-seek-indicator-value class="tabular-nums"></media-seek-indicator-value>
</media-seek-indicator>
<media-status-indicator hidden actions="togglePaused" class="group/input-indicator col-start-2 row-start-1 grid place-content-center text-center p-4 data-direction:gap-1 @2xl/media-root:data-direction:p-6 not-data-direction:bg-black/35 not-data-direction:rounded-full not-data-direction:backdrop-blur-sm not-data-direction:[transition-property:opacity,scale] not-data-direction:duration-200 not-data-direction:ease-out motion-reduce:not-data-direction:transition-opacity motion-reduce:not-data-direction:duration-50 not-data-direction:data-starting-style:opacity-0 not-data-direction:data-ending-style:opacity-0 not-data-direction:data-starting-style:scale-[0.85] not-data-direction:data-ending-style:scale-[0.85] not-data-direction:data-ending-style:duration-100 not-data-direction:data-ending-style:ease-in data-[direction=backward]:col-start-1 data-[direction=backward]:justify-self-start data-[direction=forward]:col-start-3 data-[direction=forward]:justify-self-end">
<media-icon name="play" class="hidden size-[calc(var(--media-icon-size)*1.5)] group-data-[status=play]/input-indicator:block group-data-[status=play]/input-indicator:translate-x-px"></media-icon>
<media-icon name="pause" class="hidden size-[calc(var(--media-icon-size)*1.5)] group-data-[status=pause]/input-indicator:block"></media-icon>
</media-status-indicator>
</div>
</media-container>
</video-player>
Default Audio Skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { createPlayer, Container, usePlayer, BufferingIndicator, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster, type RenderProp } from '@videojs/react';
import { Audio, audioFeatures } from '@videojs/react/audio';
import './player.css';
function PlaybackRateRadioGroup(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
const { options, setValue, value } = state;
return (
<Menu.RadioGroup className="media-menu__group" value={value} onValueChange={setValue} aria-label="Playback rate">
{options.map((option) => (
<Menu.RadioItem key={option.value} className="media-menu__item" value={option.value} disabled={option.disabled}>
<span>{option.label}</span>
<Menu.ItemIndicator checked={option.value === value} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
);
}
function PlaybackRateTrigger(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
return (
<Menu.Trigger
disabled={state.disabled}
render={<PlaybackRateButton className="media-button--playback-rate" render={<Button />} />}
/>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
export const Player = createPlayer({ features: audioFeatures });
export interface AudioPlayerProps {
src: string;
style?: CSSProperties;
className?: string;
}
/**
* @example
* ```tsx
* <AudioPlayer
* src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
* />
* ```
*/
export function AudioPlayer({ src, className, ...rest }: AudioPlayerProps): ReactNode {
return (
<Player.Provider>
<Container className={`media-default-skin media-default-skin--audio ${className ?? ''}`} {...rest}>
<Audio src={src} />
<ErrorDialog.Root>
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title">Something went wrong.</ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--subtle">OK</ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<div className="media-surface media-controls">
<Tooltip.Provider>
<div className="media-button-group">
<span className="media-button--play__wrapper">
<BufferingIndicator
render={(props) => (
<div {...props} className="media-buffering-indicator">
<SpinnerIcon className="media-icon" />
</div>
)}
/>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<PlayButton className="media-button--play" render={<Button />}>
<RestartIcon className="media-icon media-icon--restart" />
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</PlayButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
</span>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek media-icon--flipped" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
</div>
<div className="media-time-controls">
<Time.Value type="current" className="media-time" />
<TimeSlider.Root className="media-slider">
<TimeSlider.Track className="media-slider__track">
<TimeSlider.Fill className="media-slider__fill" />
<TimeSlider.Buffer className="media-slider__buffer" />
</TimeSlider.Track>
<TimeSlider.Thumb className="media-slider__thumb" />
<TimeSlider.Preview className="media-slider__preview">
<TimeSlider.Value type="pointer" className="media-slider__value media-time" />
</TimeSlider.Preview>
</TimeSlider.Root>
<Time.Value toggle type="remaining" className="media-time" />
</div>
<div className="media-button-group">
<Menu.Root side="top" align="center" boundary="viewport">
<PlaybackRateTrigger />
<Menu.Content className="media-surface media-popover media-menu media-menu--playback-rate">
<PlaybackRateRadioGroup />
</Menu.Content>
</Menu.Root>
<VolumePopover />
</div>
</Tooltip.Provider>
</div>
{/* Hotkeys */}
<Hotkey keys="Space" action="togglePaused" />
<Hotkey keys="k" action="togglePaused" />
<Hotkey keys="m" action="toggleMuted" />
<Hotkey keys="ArrowRight" action="seekStep" value={5} />
<Hotkey keys="ArrowLeft" action="seekStep" value={-5} />
<Hotkey keys="l" action="seekStep" value={10} />
<Hotkey keys="j" action="seekStep" value={-10} />
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
<Hotkey keys="0-9" action="seekToPercent" />
<Hotkey keys="Home" action="seekToPercent" value={0} />
<Hotkey keys="End" action="seekToPercent" value={100} />
<Hotkey keys=">" action="speedUp" />
<Hotkey keys="<" action="speedDown" />
{/* Input Feedback */}
<StatusAnnouncer />
</Container>
</Player.Provider>
);
}
// ================================================================
// Components
// ================================================================
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
return (
<button
ref={ref}
type="button"
className={`media-button media-button--subtle media-button--icon ${className ?? ''}`}
{...props}
/>
);
});
function VolumePopover(): ReactNode {
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
const muteButton = (
<MuteButton className="media-button--mute" render={<Button />}>
<VolumeOffIcon className="media-icon media-icon--volume-off" />
<VolumeLowIcon className="media-icon media-icon--volume-low" />
<VolumeHighIcon className="media-icon media-icon--volume-high" />
</MuteButton>
);
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top" boundary="viewport">
<Popover.Trigger render={muteButton} />
<Popover.Popup className="media-surface media-popover media-popover--volume">
<VolumeSlider.Root className="media-slider" orientation="vertical" thumbAlignment="edge">
<VolumeSlider.Track className="media-slider__track">
<VolumeSlider.Fill className="media-slider__fill" />
</VolumeSlider.Track>
<VolumeSlider.Thumb className="media-slider__thumb media-slider__thumb--persistent" />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Root>
);
}
/* ==========================================================================
Reset
========================================================================== */
.media-default-skin *,
.media-default-skin *::before,
.media-default-skin *::after {
box-sizing: border-box;
}
.media-default-skin img,
.media-default-skin video,
.media-default-skin svg {
display: block;
max-width: 100%;
}
.media-default-skin button {
font: inherit;
}
.media-default-skin [hidden][hidden] {
/* Keep authored templates hidden even when component classes set display. */
display: none;
}
@media (prefers-reduced-motion: no-preference) {
.media-default-skin {
interpolate-size: allow-keywords;
}
}
/* ==========================================================================
Root Container
========================================================================== */
.media-default-skin {
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
--media-icon-size: 18px;
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
position: relative;
display: block;
width: 100%;
height: 100%;
container: media-root / inline-size;
font-family:
Inter Variable,
Inter,
ui-sans-serif,
system-ui,
sans-serif;
font-size: 0.8125rem; /* 13px at 100% font size */
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
scrollbar-width: thin;
border-radius: var(--media-border-radius, 2rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-color-scrollbar-thumb);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
/* ==========================================================================
Surface (shared glass effect for tooltips, popovers, controls)
========================================================================== */
.media-default-skin .media-surface {
background-color: var(--media-surface-background-color);
box-shadow:
0 0 0 1px var(--media-surface-outer-border-color),
0 1px 3px 0 var(--media-surface-shadow-color),
0 1px 2px -1px var(--media-surface-shadow-color);
backdrop-filter: var(--media-surface-backdrop-filter);
/* Inner border ring */
&::after {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
content: "";
border-radius: inherit;
box-shadow: inset 0 0 0 1px var(--media-surface-inner-border-color);
}
}
/* ==========================================================================
Buffering Indicator
========================================================================== */
.media-default-skin .media-buffering-indicator {
position: absolute;
inset: 0;
z-index: 10;
display: none;
place-content: center;
color: oklch(1 0 0);
pointer-events: none;
&:not([data-visible]) {
--media-spinner-animation: none;
}
&[data-visible] {
display: grid;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-default-skin .media-error {
outline: none;
}
.media-default-skin .media-error:not([data-open]) {
display: none;
}
.media-default-skin .media-error__title {
font-weight: 600;
line-height: 1.25;
}
.media-default-skin .media-error__description {
overflow-wrap: anywhere;
opacity: 0.7;
}
.media-default-skin .media-error__actions {
display: flex;
gap: 0.5rem;
& > * {
flex: 1;
}
}
.media-default-skin .media-error[data-open] ~ .media-controls * {
visibility: hidden;
}
/* ==========================================================================
Controls
========================================================================== */
.media-default-skin .media-controls {
display: flex;
column-gap: 0.075rem;
align-items: center;
padding: 0.375rem;
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
border-radius: 1.5rem;
}
/* ==========================================================================
Time Display
========================================================================== */
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: 0.75rem;
align-items: center;
padding-inline: 0.5rem;
container: media-time-controls / inline-size;
}
.media-default-skin .media-time {
font-variant-numeric: tabular-nums;
}
.media-default-skin .media-time[role="button"] {
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: 0.25rem;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
/* ==========================================================================
Buttons
========================================================================== */
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
min-height: 0;
padding: 0.5rem 1rem;
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(infinity * 1px);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:active {
scale: 0.98;
}
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: oklch(0 0 0);
text-shadow: none;
background: oklch(1 0 0);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
text-decoration: none;
background-color: oklch(from currentColor l c h / 0.1);
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
width: 2.25rem;
aspect-ratio: 1;
padding: 0;
&:active {
scale: 0.9;
}
& .media-icon__container {
display: grid;
}
& .media-icon {
grid-area: 1 / 1;
transition-behavior: allow-discrete;
transition-property: display, opacity;
transition-duration: 150ms;
transition-timing-function: ease-out;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
/* Seek button */
.media-default-skin .media-button--seek {
& .media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 10px;
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
}
&:has(.media-icon--flipped) .media-icon__label {
right: unset;
left: -1px;
}
}
/* Playback rate button */
.media-default-skin .media-button--playback-rate {
padding: 0;
font-variant-numeric: tabular-nums;
&::after {
width: 4ch;
content: attr(data-rate) "\00D7";
}
&[data-inline-rate-label]::after {
content: none;
}
}
/* Settings button */
.media-default-skin .media-button--settings {
display: none;
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
&[aria-expanded="true"] .media-icon--settings {
transform: rotate(90deg);
}
}
.media-default-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
display: grid;
}
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
text content. */
.media-default-skin .media-button--live {
display: inline-flex;
gap: 0.4rem;
align-items: center;
width: auto;
aspect-ratio: auto;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
transition: background-color 150ms ease-out;
}
&[data-live-edge]::before {
background-color: oklch(0.65 0.22 27);
}
}
/* ==========================================================================
Button Groups
========================================================================== */
.media-default-skin .media-button-group {
display: flex;
gap: 0.075rem;
align-items: center;
@container media-root (width > 42rem) {
gap: 0.125rem;
}
}
/* ==========================================================================
Badge
========================================================================== */
.media-default-skin .media-badge {
padding: 0.125rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
border-radius: calc(infinity * 1px);
}
/* ==========================================================================
Icons
========================================================================== */
.media-default-skin .media-icon__container {
position: relative;
}
.media-default-skin .media-icon {
flex-shrink: 0;
width: var(--media-icon-size);
height: var(--media-icon-size);
}
.media-default-skin .media-icon--flipped {
scale: -1 1;
}
/* ==========================================================================
Slider
========================================================================== */
.media-default-skin .media-slider {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: calc(infinity * 1px);
&[data-orientation="horizontal"] {
width: 100%;
min-width: 5rem;
height: 2rem;
}
&[data-orientation="vertical"] {
width: 2rem;
height: 5rem;
}
}
/* Track */
.media-default-skin .media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
border-radius: inherit;
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: 0.25rem;
}
&[data-orientation="vertical"] {
width: 0.25rem;
height: 100%;
}
}
/* Thumb */
.media-default-skin .media-slider__thumb {
position: absolute;
z-index: 10;
width: 0.625rem;
height: 0.625rem;
user-select: none;
outline: 4px solid transparent;
outline-offset: -4px;
background-color: currentColor;
border-radius: calc(infinity * 1px);
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.1)),
0 1px 3px 0 oklch(0 0 0 / 0.35),
0 1px 2px -1px oklch(0 0 0 / 0.35);
opacity: 0;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, height, width, outline-offset;
&[data-orientation="horizontal"] {
top: 50%;
left: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-fill));
left: 50%;
}
&:hover,
&:focus {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
}
.media-default-skin .media-slider:active .media-slider__thumb,
.media-default-skin .media-slider__thumb--persistent {
width: 0.75rem;
height: 0.75rem;
}
.media-default-skin .media-slider:hover .media-slider__thumb,
.media-default-skin .media-slider__thumb:focus-visible,
.media-default-skin .media-slider__thumb--persistent {
opacity: 1;
}
/* Preview */
.media-default-skin .media-slider__preview {
& .media-slider__value,
&::before {
opacity: 0;
scale: 0.5;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
& .media-slider__value {
position: absolute;
bottom: 2.25rem;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
filter: blur(8px);
translate: -50% 0.5rem;
transition-property: filter, opacity, scale, translate;
}
&::before {
display: block;
min-width: 0.25rem;
height: 0.25rem;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
}
&[data-pointing] .media-slider__value,
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
&[data-pointing] .media-slider__value {
filter: blur(0);
translate: -50% 0;
}
}
/* Shared track fills */
.media-default-skin .media-slider__buffer,
.media-default-skin .media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
}
.media-default-skin .media-slider__buffer[data-orientation="horizontal"],
.media-default-skin .media-slider__fill[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
}
.media-default-skin .media-slider__buffer[data-orientation="vertical"],
.media-default-skin .media-slider__fill[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
}
/* Buffer */
.media-default-skin .media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
transition-timing-function: ease-out;
transition-duration: 0.25s;
&[data-orientation="horizontal"] {
width: var(--media-slider-buffer);
transition-property: width;
}
&[data-orientation="vertical"] {
height: var(--media-slider-buffer);
transition-property: height;
}
}
/* Fill */
.media-default-skin .media-slider__fill {
background-color: currentColor;
&[data-orientation="horizontal"] {
width: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
height: var(--media-slider-fill);
}
}
/* Dragging — thumb and fill follow the pointer position */
.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
width: var(--media-slider-pointer);
}
.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
height: var(--media-slider-pointer);
}
/* ==========================================================================
Popups & Tooltips
========================================================================== */
.media-default-skin {
--media-popup-transition:
opacity var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
filter var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
transform var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
scale var(--media-popup-transition-timing-function) var(--media-popup-transition-duration);
}
.media-default-skin .media-popover,
.media-default-skin .media-tooltip {
--popup-translate-distance: 0.5rem;
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
filter: blur(4px);
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--popup-translate-x-distance, 0), var(--popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--popup-translate-y-distance: var(--popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--popup-translate-y-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--popup-translate-x-distance: var(--popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--popup-translate-x-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: left;
}
/* Safe area between trigger and popup */
&::before {
position: absolute;
pointer-events: inherit;
content: "";
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
inset-inline: 0;
width: 100%;
}
&[data-side="top"]::before {
top: 100%;
}
&[data-side="bottom"]::before {
bottom: 100%;
}
&[data-side="left"]::before,
&[data-side="right"]::before {
inset-block: 0;
height: 100%;
}
&[data-side="left"]::before {
left: 100%;
}
&[data-side="right"]::before {
right: 100%;
}
}
.media-default-skin .media-popover {
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-popover-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-popover-side-offset);
}
}
.media-default-skin .media-popover--volume {
padding: 0.75rem 0;
border-radius: calc(infinity * 1px);
&:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
white-space: nowrap;
border-radius: calc(infinity * 1px);
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
column-gap: 0.25rem;
align-items: center;
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-tooltip-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-tooltip-side-offset);
}
& .media-tooltip__kbd {
min-width: 1.5em;
padding: 0.1em;
font-family: inherit;
font-size: 90%;
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: 0.25rem;
}
}
/* ==========================================================================
Menus
Note: Menus use `.media-popover` styles for positioning and transitions.
========================================================================== */
.media-default-skin .media-menu {
--menu-transition-duration: 250ms;
--menu-max-height: 14rem;
--menu-padding: 0.25rem;
--menu-border-radius: 0.75rem;
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--menu-transition-duration: 0ms;
}
& .media-menu__panel {
position: absolute;
inset: 0;
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-in-out;
transition-duration: var(--menu-transition-duration);
transition-property: translate, filter;
will-change: translate, filter;
&[data-starting-style],
&[data-ending-style] {
overflow: hidden;
}
/* Root settings view — slides out when a submenu is active */
&[data-menu-root-view][data-menu-view-state="inactive"] {
filter: blur(8px);
translate: -100% 0;
/* Hide the indicator while transitioning */
& .media-menu__group::before {
display: none;
}
}
/* Submenu panels — slide in/out alongside the root view */
&[data-submenu] {
z-index: 10;
&:not([data-open], [data-ending-style]) {
translate: -100% 0;
transition-property: none;
}
&[data-starting-style],
&[data-ending-style] {
pointer-events: none;
filter: blur(8px);
}
&[data-starting-style][data-direction="forward"],
&[data-ending-style][data-direction="back"] {
translate: 100% 0;
}
&[data-ending-style][data-direction="forward"],
&[data-starting-style][data-direction="back"] {
translate: -100% 0;
}
}
}
& .media-menu__separator {
margin-block: 0.25rem;
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
& .media-menu__group {
display: flex;
flex-direction: column;
gap: 0.125rem;
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --media-menu-item-highlight-anchor;
inset: anchor(inside);
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.1);
border-radius: var(--menu-item-border-radius);
transition: inset ease-in-out 100ms;
}
}
}
& .media-menu__item,
& .media-menu__back {
display: flex;
gap: 0.375rem;
align-items: center;
padding: 0.375rem 0.5rem;
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-out;
transition-duration: 100ms;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
@supports (top: anchor(top)) {
anchor-name: --media-menu-item-highlight-anchor;
background-color: transparent;
}
}
& .media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__indicator {
flex-shrink: 0;
margin-right: -0.25rem;
margin-left: auto;
opacity: 0;
& .media-icon {
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__item {
justify-content: space-between;
font-variant-numeric: tabular-nums;
color: inherit;
&[aria-disabled="true"] {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
&[aria-checked="true"] .media-menu__indicator {
opacity: 1;
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
& .media-menu__tier {
padding-top: 1px;
padding-left: 0.125rem;
font-size: 0.5625rem;
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
& .media-menu__back {
width: 100%;
margin-bottom: 0.125rem;
}
& .media-menu__hint {
display: inline-flex;
gap: 0.25rem;
align-items: center;
min-width: 0;
padding-left: 0.5rem;
margin-left: auto;
font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
max-width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
width: 0.875rem;
height: 0.875rem;
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: 12rem;
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
transition:
var(--media-popup-transition),
height var(--media-popup-transition-timing-function) var(--menu-transition-duration),
width var(--media-popup-transition-timing-function) var(--menu-transition-duration);
/* Don't transition width and height on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
/* ==========================================================================
Icon State Visibility for Audio Skins
Data-attribute-driven visibility rules for multi-state icon buttons.
Uses :is() with both element selectors (for HTML custom element wrappers)
and class selectors (for React rendered SVG elements).
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon--restart,
.media-button--play .media-icon--play,
.media-button--play .media-icon--pause,
.media-button--mute .media-icon--volume-off,
.media-button--mute .media-icon--volume-low,
.media-button--mute .media-icon--volume-high {
display: none;
opacity: 0;
}
/* --- Active icon per state --- */
/* Play: ended → restart */
.media-button--play[data-ended] .media-icon--restart,
/* Play: paused or not yet started (not ended) → play */
.media-button--play:not([data-ended])[data-paused] .media-icon--play,
.media-button--play:not([data-ended]):not([data-started]) .media-icon--play,
/* Play: started and not paused/ended → pause */
.media-button--play[data-started]:not([data-paused]):not([data-ended]) .media-icon--pause,
/* Mute: muted → volume off */
.media-button--mute[data-muted] .media-icon--volume-off,
/* Mute: volume low (not muted) → volume low */
.media-button--mute:not([data-muted])[data-volume-level="low"] .media-icon--volume-low,
/* Mute: volume high (not muted, not low) → volume high */
.media-button--mute:not([data-muted]):not([data-volume-level="low"]) .media-icon--volume-high {
display: block;
opacity: 1;
}
/* ==========================================================================
Root
========================================================================== */
.media-default-skin--audio {
--media-surface-background-color: oklch(1 0 0 / 0.5);
--media-surface-inner-border-color: oklch(1 0 0 / 0.1);
--media-surface-outer-border-color: oklch(0 0 0 / 0.05);
--media-surface-shadow-color: oklch(0 0 0 / 0.15);
--media-surface-backdrop-filter: blur(16px) saturate(1.5);
--media-text-color: var(--media-color-primary, oklch(0 0 0));
--media-error-dialog-transition-duration: 250ms;
--media-error-dialog-transition-delay: 100ms;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
--media-tooltip-side-offset: 0.75rem;
--media-tooltip-boundary-offset: 0.75rem;
--media-popover-side-offset: 0.75rem;
--media-popover-boundary-offset: 0.75rem;
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-color-scheme: dark) {
--media-surface-background-color: oklch(0 0 0 / 0.4);
--media-text-color: var(--media-color-primary, oklch(1 0 0));
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: oklch(1 0 0);
--media-surface-outer-border-color: oklch(0 0 0 / 0.05);
}
@media (prefers-color-scheme: dark) and ((prefers-reduced-transparency: reduce) or (prefers-contrast: more)) {
--media-surface-background-color: oklch(0 0 0);
--media-surface-inner-border-color: oklch(1 0 0 / 0.2);
--media-surface-outer-border-color: transparent;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-default-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: 0.75rem;
align-items: center;
padding-inline: 1.25rem 0.125rem;
color: var(--media-text-color);
background-color: var(--media-surface-background-color);
border-radius: calc(infinity * 1px);
backdrop-filter: var(--media-surface-backdrop-filter);
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: ease-out;
transition-duration: var(--media-error-dialog-transition-duration);
transition-property: opacity, filter;
}
.media-default-skin .media-error[data-starting-style] .media-error__dialog,
.media-default-skin .media-error[data-ending-style] .media-error__dialog {
opacity: 0;
filter: blur(4px);
}
.media-default-skin .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
.media-default-skin--audio .media-error__content {
display: flex;
flex: 1;
gap: 0.5rem;
align-items: center;
}
/* ==========================================================================
Controls
========================================================================== */
.media-default-skin--audio .media-controls {
color: var(--media-text-color);
}
/* ==========================================================================
Buffering (spinner over play button — overlay only, no layout change)
========================================================================== */
.media-default-skin--audio .media-button--play__wrapper {
position: relative;
display: inline-flex;
}
.media-default-skin--audio .media-button--play__wrapper .media-buffering-indicator {
color: inherit;
}
.media-default-skin--audio
.media-button--play__wrapper:has(.media-buffering-indicator[data-visible])
.media-button--play
.media-icon {
opacity: 0;
}
/* ==========================================================================
Sliders
========================================================================== */
.media-default-skin--audio .media-slider__track {
background-color: oklch(0 0 0 / 0.1);
@media (prefers-color-scheme: dark) {
background-color: oklch(1 0 0 / 0.2);
box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);
}
}
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { createPlayer, Container, usePlayer, BufferingIndicator, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster, type RenderProp } from '@videojs/react';
import { Audio, audioFeatures } from '@videojs/react/audio';
import './player.css';
const button = {
base: "flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden",
primary: "bg-white text-black font-medium text-shadow-none",
subtle: "bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10",
icon: "grid w-9 aspect-square p-0 active:scale-90",
live: "inline-flex items-center gap-1.5 aspect-auto w-auto px-3 py-2 text-xs font-semibold uppercase tracking-wider leading-none before:inline-block before:size-2 before:shrink-0 before:rounded-full before:bg-current/40 before:transition-colors before:duration-150 before:ease-out before:content-[\"\"] data-[live-edge]:before:bg-red-500",
};
const buttonGroup = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5";
const controls = "peer/controls @container/media-controls p-[0.375rem] flex items-center gap-x-[0.075rem] rounded-3xl text-shadow-2xs text-shadow-(color:--media-current-shadow-color) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 text-(--media-text-color) peer-data-open/error:**:invisible";
const error = {
root: "peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none",
dialog: "absolute inset-0 z-20 flex items-center gap-3 rounded-full px-5 pr-0.5 bg-(--media-surface-background-color) text-(--media-text-color) backdrop-blur-lg backdrop-saturate-150 transition-[opacity,filter] ease-out duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-xs group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-xs group-data-ending-style/error:delay-0",
content: "flex flex-1 items-center gap-2",
title: "font-semibold leading-tight",
description: "opacity-70 wrap-anywhere",
actions: "flex gap-2 *:flex-1",
};
const icon = "block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out";
const iconContainer = "relative grid";
const iconFlipped = "[scale:-1_1]";
const iconState = {
play: {
button: "group",
restart: "hidden opacity-0 group-data-ended:block group-data-ended:opacity-100",
play: "hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100",
pause: "hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100",
},
mute: {
button: "group",
volumeOff: "hidden opacity-0 group-data-muted:block group-data-muted:opacity-100",
volumeLow: "hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100",
volumeHigh: "hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100",
},
fullscreen: {
button: "group",
enter: "hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100",
exit: "hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100",
},
captions: {
button: "group",
off: "hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100",
on: "hidden opacity-0 group-data-active:block group-data-active:opacity-100",
},
pip: {
button: "group",
off: "hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100",
on: "hidden opacity-0 group-data-pip:block group-data-pip:opacity-100",
},
cast: {
button: "group",
enter: "hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100",
exit: "hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100",
},
airplay: {
button: "group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]",
enter: "hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100",
exit: "hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100",
},
};
const menu = {
root: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.25rem] [--menu-border-radius:0.75rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none overflow-auto!",
settings: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.25rem] [--menu-border-radius:0.75rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none [--media-popup-transition:var(--media-popup-base-transition),height_var(--media-popup-transition-timing-function)_var(--menu-transition-duration),width_var(--media-popup-transition-timing-function)_var(--menu-transition-duration)] data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)] data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)] min-w-48 w-(--media-menu-width) h-(--media-menu-height) !overflow-hidden",
group: "flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden",
item: "flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50",
separator: "my-1 border-b border-[oklch(0_0_0/0.1)] shadow-[0_1px_0_0_oklch(1_0_0/0.075)]",
tier: "pl-0.5 pt-px text-[0.5625rem] font-semibold leading-none text-current/70",
indicator: "ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100",
icon: "shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]",
rootView: "absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] group/menu-root-view data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur",
submenuPanel: "absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full",
back: "flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full",
hint: "ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65",
hintLabel: "max-w-24 overflow-hidden text-ellipsis whitespace-nowrap",
chevron: "size-3.5",
settingsGroup: "group/settings",
settingsTrigger: "group hidden group-has-[[data-availability=available]]/settings:grid",
settingsIcon: "transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0",
};
const playButton = {
wrapper: "group/play inline-flex relative",
control: "peer-data-visible/play-buffering:[&>svg]:opacity-0",
bufferingRoot: "peer/play-buffering absolute inset-0 z-10 hidden place-content-center pointer-events-none text-inherit not-data-visible:[--media-spinner-animation:none] data-visible:grid",
};
const playbackRate = {
button: "tabular-nums after:w-[4ch] after:content-[attr(data-rate)_'×'] data-[inline-rate-label]:after:content-none",
};
const popup = {
popover: "bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset)",
tooltip: "bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)",
volume: "py-3 px-0 rounded-full",
tooltipShortcut: "min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded",
};
const root = "**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,2rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 scrollbar-thin scrollbar-thumb-current/30 [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-auto [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-thumb-current/80 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] [--media-text-color:var(--media-color-primary,oklch(0_0_0))] [--media-surface-background-color:oklch(1_0_0/0.5)] [--media-surface-inner-border-color:oklch(1_0_0/0.1)] [--media-surface-outer-border-color:oklch(0_0_0/0.05)] [--media-surface-shadow-color:oklch(0_0_0/0.15)] [--media-surface-backdrop-filter:blur(16px)_saturate(1.5)] [--media-error-dialog-transition-duration:250ms] [--media-error-dialog-transition-delay:100ms] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-tooltip-side-offset:0.75rem] [--media-tooltip-boundary-offset:0.75rem] [--media-popover-side-offset:0.75rem] [--media-popover-boundary-offset:0.75rem] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-popup-transition-duration:0ms] dark:[--media-surface-background-color:oklch(0_0_0/0.4)] dark:[--media-text-color:var(--media-color-primary,oklch(1_0_0))] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(1_0_0)] contrast-more:[--media-surface-background-color:oklch(1_0_0)] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:oklch(0_0_0/0.05)] contrast-more:[--media-surface-outer-border-color:oklch(0_0_0/0.05)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] dark:contrast-more:[--media-surface-background-color:oklch(0_0_0)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-inner-border-color:oklch(1_0_0/0.2)] dark:contrast-more:[--media-surface-inner-border-color:oklch(1_0_0/0.2)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:transparent] dark:contrast-more:[--media-surface-outer-border-color:transparent]";
const seek = {
label: "text-[10px] font-medium tracking-tighter tabular-nums",
labelForward: "absolute -right-px -bottom-0.75",
labelBackward: "absolute -left-px -bottom-0.75",
};
const slider = {
root: "group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20",
track: "relative isolate overflow-hidden rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1 data-[orientation=vertical]:h-full bg-black/10 dark:bg-white/20 dark:ring-1 dark:ring-black/5",
fill: {
base: "absolute rounded-[inherit] pointer-events-none",
fill: "bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)",
buffer: "bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)",
},
thumb: {
base: "z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.35),0_1px_2px_-1px_oklch(0_0_0/0.35)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/15 hover:outline-offset-0 focus-visible:outline-current/15 focus-visible:outline-offset-0 after:absolute after:-inset-1 after:rounded-[inherit] after:shadow-[0_0_0_2px_currentColor] after:pointer-events-none after:transition-[opacity,scale] after:duration-150 after:ease-out after:opacity-0 after:scale-50 focus-visible:after:opacity-100 focus-visible:after:scale-100 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))]",
persistent: "size-3",
interactive: "size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3",
},
preview: "group/preview before:block before:min-w-1 before:h-1 before:bg-current before:rounded-full before:opacity-0 before:scale-50 before:shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_2px_0_oklch(0_0_0/0.35)] before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden",
value: "absolute bottom-9 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none",
};
const time = {
group: "@container/media-time flex items-center flex-1 gap-3 px-2",
current: "hidden @2xs/media-time:block tabular-nums",
duration: "tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2",
};
interface AudioSkinProps {
src: string;
style?: CSSProperties;
className?: string;
}
function PlaybackRateRadioGroup(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
const { options, setValue, value } = state;
return (
<Menu.RadioGroup className={menu.group} value={value} onValueChange={setValue} aria-label="Playback rate">
{options.map((option) => (
<Menu.RadioItem key={option.value} className={menu.item} value={option.value} disabled={option.disabled}>
<span>{option.label}</span>
<Menu.ItemIndicator checked={option.value === value} forceMount className={menu.indicator}>
<CheckIcon className={`${icon} ${menu.icon}`} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
);
}
function PlaybackRateTrigger(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
return (
<Menu.Trigger
disabled={state.disabled}
render={<PlaybackRateButton className={playbackRate.button} render={<Button />} />}
/>
);
}
export function AudioSkinTailwind({ children, className, ...rest }: AudioSkinProps): ReactNode {
return (
<Container className={`${root} ${className ?? ''}`} {...rest}>
{children}
<ErrorDialog.Root>
<ErrorDialog.Popup className={error.root}>
<div className={error.dialog}>
<div className={error.content}>
<ErrorDialog.Title className={error.title}>Something went wrong.</ErrorDialog.Title>
<ErrorDialog.Description className={error.description} />
</div>
<div className={error.actions}>
<ErrorDialog.Close className={`${button.base} ${button.subtle}`}>OK</ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<div className={controls}>
<Tooltip.Provider>
<div className={buttonGroup}>
<span className={playButton.wrapper}>
<BufferingIndicator
render={(props) => (
<div {...props} className={`${playButton.bufferingRoot} ${props.className}`}>
<SpinnerIcon className={icon} />
</div>
)}
/>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<PlayButton className={`${iconState.play.button} ${playButton.control}`} render={<Button />}>
<RestartIcon className={`${icon} ${iconState.play.restart}`} />
<PlayIcon className={`${icon} ${iconState.play.play}`} />
<PauseIcon className={`${icon} ${iconState.play.pause}`} />
</PlayButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
</span>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={`${icon} ${iconFlipped}`} />
<span className={`${seek.label} ${seek.labelBackward}`}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={icon} />
<span className={`${seek.label} ${seek.labelForward}`}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
</div>
<div className={time.group}>
<Time.Value type="current" className={time.current} />
<TimeSlider.Root render={<SliderRoot />}>
<TimeSlider.Track render={<SliderTrack />}>
<TimeSlider.Fill render={<SliderFill />} />
<TimeSlider.Buffer render={<SliderBuffer />} />
</TimeSlider.Track>
<TimeSlider.Thumb render={<SliderThumb />} />
<TimeSlider.Preview className={slider.preview}>
<TimeSlider.Value type="pointer" className={slider.value} />
</TimeSlider.Preview>
</TimeSlider.Root>
<Time.Value toggle type="remaining" className={time.duration} />
</div>
<div className={buttonGroup}>
<Menu.Root side="top" align="center" boundary="viewport">
<PlaybackRateTrigger />
<Menu.Content className={`${popup.popover} ${menu.root}`}>
<PlaybackRateRadioGroup />
</Menu.Content>
</Menu.Root>
<VolumePopover />
</div>
</Tooltip.Provider>
</div>
{/* Hotkeys */}
<Hotkey keys="Space" action="togglePaused" />
<Hotkey keys="k" action="togglePaused" />
<Hotkey keys="m" action="toggleMuted" />
<Hotkey keys="ArrowRight" action="seekStep" value={5} />
<Hotkey keys="ArrowLeft" action="seekStep" value={-5} />
<Hotkey keys="l" action="seekStep" value={10} />
<Hotkey keys="j" action="seekStep" value={-10} />
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
<Hotkey keys="0-9" action="seekToPercent" />
<Hotkey keys="Home" action="seekToPercent" value={0} />
<Hotkey keys="End" action="seekToPercent" value={100} />
<Hotkey keys=">" action="speedUp" />
<Hotkey keys="<" action="speedDown" />
{/* Input Feedback */}
<StatusAnnouncer />
</Container>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
// ================================================================
// Components
// ================================================================
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
return (
<button ref={ref} type="button" className={`${button.base} ${button.subtle} ${button.icon} ${className ?? ''}`} {...props} />
);
});
const SliderRoot = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderRoot({ className, ...props }, ref) {
return <div ref={ref} className={`${slider.root} ${className ?? ''}`} {...props} />;
});
const SliderTrack = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderTrack(
{ className, ...props },
ref
) {
return <div ref={ref} className={`${slider.track} ${className ?? ''}`} {...props} />;
});
const SliderFill = forwardRef<HTMLDivElement, ComponentProps<'div'> & { type?: 'fill' | 'buffer' }>(function SliderFill(
{ type = 'fill', className, ...props },
ref
) {
return (
<div
ref={ref}
className={`${slider.fill.base} ${type === 'fill' ? slider.fill.fill : slider.fill.buffer} ${className ?? ''}`}
{...props}
/>
);
});
const SliderBuffer = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderBuffer(props, ref) {
return <SliderFill type="buffer" ref={ref} {...props} />;
});
const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persistent?: boolean }>(function SliderThumb(
{ persistent, className, ...props },
ref
) {
return (
<div
ref={ref}
className={`${slider.thumb.base} ${persistent ? slider.thumb.persistent : slider.thumb.interactive} ${className ?? ''}`}
{...props}
/>
);
});
function VolumePopover(): ReactNode {
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
const muteButton = (
<MuteButton className={iconState.mute.button} render={<Button />}>
<VolumeOffIcon className={`${icon} ${iconState.mute.volumeOff}`} />
<VolumeLowIcon className={`${icon} ${iconState.mute.volumeLow}`} />
<VolumeHighIcon className={`${icon} ${iconState.mute.volumeHigh}`} />
</MuteButton>
);
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top" boundary="viewport">
<Popover.Trigger render={muteButton} />
<Popover.Popup className={`${popup.popover} ${popup.volume}`}>
<VolumeSlider.Root orientation="vertical" thumbAlignment="edge" render={<SliderRoot />}>
<VolumeSlider.Track render={<SliderTrack />}>
<VolumeSlider.Fill render={<SliderFill />} />
</VolumeSlider.Track>
<VolumeSlider.Thumb render={(props) => <SliderThumb persistent {...props} />} />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Root>
);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/audio-ui.js"></script>
<link rel="stylesheet" href="./player.css">
<audio-player>
<media-container class="media-default-skin media-default-skin--audio">
<audio src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"></audio>
<media-error-dialog class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-alert-dialog-title class="media-error__title">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="media-error__description"></media-alert-dialog-description>
</div>
<div class="media-error__actions">
<media-alert-dialog-close class="media-button media-button--subtle">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<div class="media-surface media-controls">
<media-tooltip-group>
<div class="media-button-group">
<span class="media-button--play__wrapper">
<media-buffering-indicator class="media-buffering-indicator">
<media-icon name="spinner" class="media-icon"></media-icon>
</media-buffering-indicator>
<media-play-button commandfor="play-tooltip" class="media-button media-button--subtle media-button--icon media-button--play">
<media-icon name="restart" class="media-icon media-icon--restart"></media-icon>
<media-icon name="play" class="media-icon media-icon--play"></media-icon>
<media-icon name="pause" class="media-icon media-icon--pause"></media-icon>
</media-play-button>
<media-tooltip id="play-tooltip" side="top" boundary="viewport" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
</span>
<media-seek-button commandfor="seek-backward-tooltip" seconds="-10" class="media-button media-button--subtle media-button--icon media-button--seek">
<span class="media-icon__container">
<media-icon name="seek" class="media-icon media-icon--seek media-icon--flipped"></media-icon>
<span class="media-icon__label">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-backward-tooltip" side="top" boundary="viewport" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-forward-tooltip" seconds="10" class="media-button media-button--subtle media-button--icon media-button--seek">
<span class="media-icon__container">
<media-icon name="seek" class="media-icon media-icon--seek"></media-icon>
<span class="media-icon__label">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-forward-tooltip" side="top" boundary="viewport" class="media-surface media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
</div>
<div class="media-time-controls">
<media-time type="current" class="media-time"></media-time>
<media-time-slider class="media-slider">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
<media-slider-buffer class="media-slider__buffer"></media-slider-buffer>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb"></media-slider-thumb>
<media-slider-preview class="media-slider__preview">
<media-slider-value type="pointer" class="media-slider__value media-time"></media-slider-value>
</media-slider-preview>
</media-time-slider>
<media-time toggle type="remaining" class="media-time"></media-time>
</div>
<div class="media-button-group">
<media-playback-rate-button commandfor="playback-rate-menu" class="media-button media-button--subtle media-button--icon media-button--playback-rate"></media-playback-rate-button>
<media-menu id="playback-rate-menu" side="top" align="center" boundary="viewport" class="media-surface media-popover media-menu">
<media-playback-rate-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
</media-menu>
<media-mute-button commandfor="audio-volume-popover" class="media-button media-button--subtle media-button--icon media-button--mute">
<media-icon name="volume-off" class="media-icon media-icon--volume-off"></media-icon>
<media-icon name="volume-low" class="media-icon media-icon--volume-low"></media-icon>
<media-icon name="volume-high" class="media-icon media-icon--volume-high"></media-icon>
</media-mute-button>
<media-popover id="audio-volume-popover" open-on-hover delay="200" close-delay="100" side="top" boundary="viewport" class="media-surface media-popover media-popover--volume">
<media-volume-slider class="media-slider" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb media-slider__thumb--persistent"></media-slider-thumb>
</media-volume-slider>
</media-popover>
</div>
</media-tooltip-group>
</div>
<!-- Hotkeys -->
<media-hotkey keys="Space" action="togglePaused"></media-hotkey>
<media-hotkey keys="k" action="togglePaused"></media-hotkey>
<media-hotkey keys="m" action="toggleMuted"></media-hotkey>
<media-hotkey keys="ArrowRight" action="seekStep" value="5"></media-hotkey>
<media-hotkey keys="ArrowLeft" action="seekStep" value="-5"></media-hotkey>
<media-hotkey keys="l" action="seekStep" value="10"></media-hotkey>
<media-hotkey keys="j" action="seekStep" value="-10"></media-hotkey>
<media-hotkey keys="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
<media-hotkey keys="0-9" action="seekToPercent"></media-hotkey>
<media-hotkey keys="Home" action="seekToPercent" value="0"></media-hotkey>
<media-hotkey keys="End" action="seekToPercent" value="100"></media-hotkey>
<media-hotkey keys=">" action="speedUp"></media-hotkey>
<media-hotkey keys="<" action="speedDown"></media-hotkey>
</media-container>
</audio-player>
/* -------------------------------------------------------------------------- */
/* Global styles for the host document, outside of the Shadow DOM */
/* -------------------------------------------------------------------------- */
video-player,
live-video-player {
display: contents;
}
/*
Required to override any default video and image styles (such as
Tailwind's CSS reset) and ensure they fill the container as expected.
*/
video-player video,
video-player [slot="poster"],
live-video-player video,
live-video-player [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
video-player video::-webkit-media-text-track-container,
live-video-player video::-webkit-media-text-track-container {
z-index: 1;
font-family: inherit;
scale: 0.98;
translate: 0 var(--media-caption-track-y, 0);
transition: translate var(--media-caption-track-duration, 0) ease-out;
transition-delay: var(--media-caption-track-delay, 0);
}
/* -------------------------------------------------------------------------- */
/* Shared styles for all HTML skins */
/* -------------------------------------------------------------------------- */
media-tooltip-group {
display: contents;
}
:host {
/* `display:grid` fixes a weird issue with Safari when setting aspect-ratio */
display: grid;
width: 100%;
}
media-container {
min-width: 0;
min-height: 0;
}
/* Hide volume popover when volume control is unsupported (e.g., iOS Safari). */
.media-popover--volume:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
/* ==========================================================================
Reset
========================================================================== */
.media-default-skin *,
.media-default-skin *::before,
.media-default-skin *::after {
box-sizing: border-box;
}
.media-default-skin img,
.media-default-skin video,
.media-default-skin svg {
display: block;
max-width: 100%;
}
.media-default-skin button {
font: inherit;
}
.media-default-skin [hidden][hidden] {
/* Keep authored templates hidden even when component classes set display. */
display: none;
}
@media (prefers-reduced-motion: no-preference) {
.media-default-skin {
interpolate-size: allow-keywords;
}
}
/* ==========================================================================
Root Container
========================================================================== */
.media-default-skin {
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
--media-icon-size: 18px;
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
position: relative;
display: block;
width: 100%;
height: 100%;
container: media-root / inline-size;
font-family:
Inter Variable,
Inter,
ui-sans-serif,
system-ui,
sans-serif;
font-size: 0.8125rem; /* 13px at 100% font size */
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
scrollbar-width: thin;
border-radius: var(--media-border-radius, 2rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-color-scrollbar-thumb);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
/* ==========================================================================
Surface (shared glass effect for tooltips, popovers, controls)
========================================================================== */
.media-default-skin .media-surface {
background-color: var(--media-surface-background-color);
box-shadow:
0 0 0 1px var(--media-surface-outer-border-color),
0 1px 3px 0 var(--media-surface-shadow-color),
0 1px 2px -1px var(--media-surface-shadow-color);
backdrop-filter: var(--media-surface-backdrop-filter);
/* Inner border ring */
&::after {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
content: "";
border-radius: inherit;
box-shadow: inset 0 0 0 1px var(--media-surface-inner-border-color);
}
}
/* ==========================================================================
Buffering Indicator
========================================================================== */
.media-default-skin .media-buffering-indicator {
position: absolute;
inset: 0;
z-index: 10;
display: none;
place-content: center;
color: oklch(1 0 0);
pointer-events: none;
&:not([data-visible]) {
--media-spinner-animation: none;
}
&[data-visible] {
display: grid;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-default-skin .media-error {
outline: none;
}
.media-default-skin .media-error:not([data-open]) {
display: none;
}
.media-default-skin .media-error__title {
font-weight: 600;
line-height: 1.25;
}
.media-default-skin .media-error__description {
overflow-wrap: anywhere;
opacity: 0.7;
}
.media-default-skin .media-error__actions {
display: flex;
gap: 0.5rem;
& > * {
flex: 1;
}
}
.media-default-skin .media-error[data-open] ~ .media-controls * {
visibility: hidden;
}
/* ==========================================================================
Controls
========================================================================== */
.media-default-skin .media-controls {
display: flex;
column-gap: 0.075rem;
align-items: center;
padding: 0.375rem;
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
border-radius: 1.5rem;
}
/* ==========================================================================
Time Display
========================================================================== */
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: 0.75rem;
align-items: center;
padding-inline: 0.5rem;
container: media-time-controls / inline-size;
}
.media-default-skin .media-time {
font-variant-numeric: tabular-nums;
}
.media-default-skin .media-time[role="button"] {
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: 0.25rem;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
/* ==========================================================================
Buttons
========================================================================== */
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
min-height: 0;
padding: 0.5rem 1rem;
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(infinity * 1px);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:active {
scale: 0.98;
}
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: oklch(0 0 0);
text-shadow: none;
background: oklch(1 0 0);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
text-decoration: none;
background-color: oklch(from currentColor l c h / 0.1);
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
width: 2.25rem;
aspect-ratio: 1;
padding: 0;
&:active {
scale: 0.9;
}
& .media-icon__container {
display: grid;
}
& .media-icon {
grid-area: 1 / 1;
transition-behavior: allow-discrete;
transition-property: display, opacity;
transition-duration: 150ms;
transition-timing-function: ease-out;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
/* Seek button */
.media-default-skin .media-button--seek {
& .media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 10px;
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
}
&:has(.media-icon--flipped) .media-icon__label {
right: unset;
left: -1px;
}
}
/* Playback rate button */
.media-default-skin .media-button--playback-rate {
padding: 0;
font-variant-numeric: tabular-nums;
&::after {
width: 4ch;
content: attr(data-rate) "\00D7";
}
&[data-inline-rate-label]::after {
content: none;
}
}
/* Settings button */
.media-default-skin .media-button--settings {
display: none;
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
&[aria-expanded="true"] .media-icon--settings {
transform: rotate(90deg);
}
}
.media-default-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
display: grid;
}
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
text content. */
.media-default-skin .media-button--live {
display: inline-flex;
gap: 0.4rem;
align-items: center;
width: auto;
aspect-ratio: auto;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
transition: background-color 150ms ease-out;
}
&[data-live-edge]::before {
background-color: oklch(0.65 0.22 27);
}
}
/* ==========================================================================
Button Groups
========================================================================== */
.media-default-skin .media-button-group {
display: flex;
gap: 0.075rem;
align-items: center;
@container media-root (width > 42rem) {
gap: 0.125rem;
}
}
/* ==========================================================================
Badge
========================================================================== */
.media-default-skin .media-badge {
padding: 0.125rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
border-radius: calc(infinity * 1px);
}
/* ==========================================================================
Icons
========================================================================== */
.media-default-skin .media-icon__container {
position: relative;
}
.media-default-skin .media-icon {
flex-shrink: 0;
width: var(--media-icon-size);
height: var(--media-icon-size);
}
.media-default-skin .media-icon--flipped {
scale: -1 1;
}
/* ==========================================================================
Slider
========================================================================== */
.media-default-skin .media-slider {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: calc(infinity * 1px);
&[data-orientation="horizontal"] {
width: 100%;
min-width: 5rem;
height: 2rem;
}
&[data-orientation="vertical"] {
width: 2rem;
height: 5rem;
}
}
/* Track */
.media-default-skin .media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
border-radius: inherit;
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: 0.25rem;
}
&[data-orientation="vertical"] {
width: 0.25rem;
height: 100%;
}
}
/* Thumb */
.media-default-skin .media-slider__thumb {
position: absolute;
z-index: 10;
width: 0.625rem;
height: 0.625rem;
user-select: none;
outline: 4px solid transparent;
outline-offset: -4px;
background-color: currentColor;
border-radius: calc(infinity * 1px);
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.1)),
0 1px 3px 0 oklch(0 0 0 / 0.35),
0 1px 2px -1px oklch(0 0 0 / 0.35);
opacity: 0;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, height, width, outline-offset;
&[data-orientation="horizontal"] {
top: 50%;
left: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-fill));
left: 50%;
}
&:hover,
&:focus {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
}
.media-default-skin .media-slider:active .media-slider__thumb,
.media-default-skin .media-slider__thumb--persistent {
width: 0.75rem;
height: 0.75rem;
}
.media-default-skin .media-slider:hover .media-slider__thumb,
.media-default-skin .media-slider__thumb:focus-visible,
.media-default-skin .media-slider__thumb--persistent {
opacity: 1;
}
/* Preview */
.media-default-skin .media-slider__preview {
& .media-slider__value,
&::before {
opacity: 0;
scale: 0.5;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
& .media-slider__value {
position: absolute;
bottom: 2.25rem;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
filter: blur(8px);
translate: -50% 0.5rem;
transition-property: filter, opacity, scale, translate;
}
&::before {
display: block;
min-width: 0.25rem;
height: 0.25rem;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
}
&[data-pointing] .media-slider__value,
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
&[data-pointing] .media-slider__value {
filter: blur(0);
translate: -50% 0;
}
}
/* Shared track fills */
.media-default-skin .media-slider__buffer,
.media-default-skin .media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
}
.media-default-skin .media-slider__buffer[data-orientation="horizontal"],
.media-default-skin .media-slider__fill[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
}
.media-default-skin .media-slider__buffer[data-orientation="vertical"],
.media-default-skin .media-slider__fill[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
}
/* Buffer */
.media-default-skin .media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
transition-timing-function: ease-out;
transition-duration: 0.25s;
&[data-orientation="horizontal"] {
width: var(--media-slider-buffer);
transition-property: width;
}
&[data-orientation="vertical"] {
height: var(--media-slider-buffer);
transition-property: height;
}
}
/* Fill */
.media-default-skin .media-slider__fill {
background-color: currentColor;
&[data-orientation="horizontal"] {
width: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
height: var(--media-slider-fill);
}
}
/* Dragging — thumb and fill follow the pointer position */
.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
.media-default-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
width: var(--media-slider-pointer);
}
.media-default-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
height: var(--media-slider-pointer);
}
/* ==========================================================================
Popups & Tooltips
========================================================================== */
.media-default-skin {
--media-popup-transition:
opacity var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
filter var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
transform var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
scale var(--media-popup-transition-timing-function) var(--media-popup-transition-duration);
}
.media-default-skin .media-popover,
.media-default-skin .media-tooltip {
--popup-translate-distance: 0.5rem;
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
filter: blur(4px);
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--popup-translate-x-distance, 0), var(--popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--popup-translate-y-distance: var(--popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--popup-translate-y-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--popup-translate-x-distance: var(--popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--popup-translate-x-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: left;
}
/* Safe area between trigger and popup */
&::before {
position: absolute;
pointer-events: inherit;
content: "";
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
inset-inline: 0;
width: 100%;
}
&[data-side="top"]::before {
top: 100%;
}
&[data-side="bottom"]::before {
bottom: 100%;
}
&[data-side="left"]::before,
&[data-side="right"]::before {
inset-block: 0;
height: 100%;
}
&[data-side="left"]::before {
left: 100%;
}
&[data-side="right"]::before {
right: 100%;
}
}
.media-default-skin .media-popover {
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-popover-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-popover-side-offset);
}
}
.media-default-skin .media-popover--volume {
padding: 0.75rem 0;
border-radius: calc(infinity * 1px);
&:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
white-space: nowrap;
border-radius: calc(infinity * 1px);
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
column-gap: 0.25rem;
align-items: center;
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-tooltip-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-tooltip-side-offset);
}
& .media-tooltip__kbd {
min-width: 1.5em;
padding: 0.1em;
font-family: inherit;
font-size: 90%;
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: 0.25rem;
}
}
/* ==========================================================================
Menus
Note: Menus use `.media-popover` styles for positioning and transitions.
========================================================================== */
.media-default-skin .media-menu {
--menu-transition-duration: 250ms;
--menu-max-height: 14rem;
--menu-padding: 0.25rem;
--menu-border-radius: 0.75rem;
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--menu-transition-duration: 0ms;
}
& .media-menu__panel {
position: absolute;
inset: 0;
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-in-out;
transition-duration: var(--menu-transition-duration);
transition-property: translate, filter;
will-change: translate, filter;
&[data-starting-style],
&[data-ending-style] {
overflow: hidden;
}
/* Root settings view — slides out when a submenu is active */
&[data-menu-root-view][data-menu-view-state="inactive"] {
filter: blur(8px);
translate: -100% 0;
/* Hide the indicator while transitioning */
& .media-menu__group::before {
display: none;
}
}
/* Submenu panels — slide in/out alongside the root view */
&[data-submenu] {
z-index: 10;
&:not([data-open], [data-ending-style]) {
translate: -100% 0;
transition-property: none;
}
&[data-starting-style],
&[data-ending-style] {
pointer-events: none;
filter: blur(8px);
}
&[data-starting-style][data-direction="forward"],
&[data-ending-style][data-direction="back"] {
translate: 100% 0;
}
&[data-ending-style][data-direction="forward"],
&[data-starting-style][data-direction="back"] {
translate: -100% 0;
}
}
}
& .media-menu__separator {
margin-block: 0.25rem;
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
& .media-menu__group {
display: flex;
flex-direction: column;
gap: 0.125rem;
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --media-menu-item-highlight-anchor;
inset: anchor(inside);
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.1);
border-radius: var(--menu-item-border-radius);
transition: inset ease-in-out 100ms;
}
}
}
& .media-menu__item,
& .media-menu__back {
display: flex;
gap: 0.375rem;
align-items: center;
padding: 0.375rem 0.5rem;
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-out;
transition-duration: 100ms;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
@supports (top: anchor(top)) {
anchor-name: --media-menu-item-highlight-anchor;
background-color: transparent;
}
}
& .media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__indicator {
flex-shrink: 0;
margin-right: -0.25rem;
margin-left: auto;
opacity: 0;
& .media-icon {
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__item {
justify-content: space-between;
font-variant-numeric: tabular-nums;
color: inherit;
&[aria-disabled="true"] {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
&[aria-checked="true"] .media-menu__indicator {
opacity: 1;
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
& .media-menu__tier {
padding-top: 1px;
padding-left: 0.125rem;
font-size: 0.5625rem;
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
& .media-menu__back {
width: 100%;
margin-bottom: 0.125rem;
}
& .media-menu__hint {
display: inline-flex;
gap: 0.25rem;
align-items: center;
min-width: 0;
padding-left: 0.5rem;
margin-left: auto;
font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
max-width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
width: 0.875rem;
height: 0.875rem;
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: 12rem;
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
transition:
var(--media-popup-transition),
height var(--media-popup-transition-timing-function) var(--menu-transition-duration),
width var(--media-popup-transition-timing-function) var(--menu-transition-duration);
/* Don't transition width and height on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
/* ==========================================================================
Icon State Visibility for Audio Skins
Data-attribute-driven visibility rules for multi-state icon buttons.
Uses :is() with both element selectors (for HTML custom element wrappers)
and class selectors (for React rendered SVG elements).
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon--restart,
.media-button--play .media-icon--play,
.media-button--play .media-icon--pause,
.media-button--mute .media-icon--volume-off,
.media-button--mute .media-icon--volume-low,
.media-button--mute .media-icon--volume-high {
display: none;
opacity: 0;
}
/* --- Active icon per state --- */
/* Play: ended → restart */
.media-button--play[data-ended] .media-icon--restart,
/* Play: paused or not yet started (not ended) → play */
.media-button--play:not([data-ended])[data-paused] .media-icon--play,
.media-button--play:not([data-ended]):not([data-started]) .media-icon--play,
/* Play: started and not paused/ended → pause */
.media-button--play[data-started]:not([data-paused]):not([data-ended]) .media-icon--pause,
/* Mute: muted → volume off */
.media-button--mute[data-muted] .media-icon--volume-off,
/* Mute: volume low (not muted) → volume low */
.media-button--mute:not([data-muted])[data-volume-level="low"] .media-icon--volume-low,
/* Mute: volume high (not muted, not low) → volume high */
.media-button--mute:not([data-muted]):not([data-volume-level="low"]) .media-icon--volume-high {
display: block;
opacity: 1;
}
/* ==========================================================================
Root
========================================================================== */
.media-default-skin--audio {
--media-surface-background-color: oklch(1 0 0 / 0.5);
--media-surface-inner-border-color: oklch(1 0 0 / 0.1);
--media-surface-outer-border-color: oklch(0 0 0 / 0.05);
--media-surface-shadow-color: oklch(0 0 0 / 0.15);
--media-surface-backdrop-filter: blur(16px) saturate(1.5);
--media-text-color: var(--media-color-primary, oklch(0 0 0));
--media-error-dialog-transition-duration: 250ms;
--media-error-dialog-transition-delay: 100ms;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
--media-tooltip-side-offset: 0.75rem;
--media-tooltip-boundary-offset: 0.75rem;
--media-popover-side-offset: 0.75rem;
--media-popover-boundary-offset: 0.75rem;
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-color-scheme: dark) {
--media-surface-background-color: oklch(0 0 0 / 0.4);
--media-text-color: var(--media-color-primary, oklch(1 0 0));
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: oklch(1 0 0);
--media-surface-outer-border-color: oklch(0 0 0 / 0.05);
}
@media (prefers-color-scheme: dark) and ((prefers-reduced-transparency: reduce) or (prefers-contrast: more)) {
--media-surface-background-color: oklch(0 0 0);
--media-surface-inner-border-color: oklch(1 0 0 / 0.2);
--media-surface-outer-border-color: transparent;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-default-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: 0.75rem;
align-items: center;
padding-inline: 1.25rem 0.125rem;
color: var(--media-text-color);
background-color: var(--media-surface-background-color);
border-radius: calc(infinity * 1px);
backdrop-filter: var(--media-surface-backdrop-filter);
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: ease-out;
transition-duration: var(--media-error-dialog-transition-duration);
transition-property: opacity, filter;
}
.media-default-skin .media-error[data-starting-style] .media-error__dialog,
.media-default-skin .media-error[data-ending-style] .media-error__dialog {
opacity: 0;
filter: blur(4px);
}
.media-default-skin .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
.media-default-skin--audio .media-error__content {
display: flex;
flex: 1;
gap: 0.5rem;
align-items: center;
}
/* ==========================================================================
Controls
========================================================================== */
.media-default-skin--audio .media-controls {
color: var(--media-text-color);
}
/* ==========================================================================
Buffering (spinner over play button — overlay only, no layout change)
========================================================================== */
.media-default-skin--audio .media-button--play__wrapper {
position: relative;
display: inline-flex;
}
.media-default-skin--audio .media-button--play__wrapper .media-buffering-indicator {
color: inherit;
}
.media-default-skin--audio
.media-button--play__wrapper:has(.media-buffering-indicator[data-visible])
.media-button--play
.media-icon {
opacity: 0;
}
/* ==========================================================================
Sliders
========================================================================== */
.media-default-skin--audio .media-slider__track {
background-color: oklch(0 0 0 / 0.1);
@media (prefers-color-scheme: dark) {
background-color: oklch(1 0 0 / 0.2);
box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);
}
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/audio-ui.js"></script>
<link rel="stylesheet" href="./player.css">
<audio-player>
<media-container class="**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,2rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 scrollbar-thin scrollbar-thumb-current/30 [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-auto [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-thumb-current/80 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] [--media-text-color:var(--media-color-primary,oklch(0_0_0))] [--media-surface-background-color:oklch(1_0_0/0.5)] [--media-surface-inner-border-color:oklch(1_0_0/0.1)] [--media-surface-outer-border-color:oklch(0_0_0/0.05)] [--media-surface-shadow-color:oklch(0_0_0/0.15)] [--media-surface-backdrop-filter:blur(16px)_saturate(1.5)] [--media-error-dialog-transition-duration:250ms] [--media-error-dialog-transition-delay:100ms] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-tooltip-side-offset:0.75rem] [--media-tooltip-boundary-offset:0.75rem] [--media-popover-side-offset:0.75rem] [--media-popover-boundary-offset:0.75rem] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-popup-transition-duration:0ms] dark:[--media-surface-background-color:oklch(0_0_0/0.4)] dark:[--media-text-color:var(--media-color-primary,oklch(1_0_0))] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(1_0_0)] contrast-more:[--media-surface-background-color:oklch(1_0_0)] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:oklch(0_0_0/0.05)] contrast-more:[--media-surface-outer-border-color:oklch(0_0_0/0.05)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] dark:contrast-more:[--media-surface-background-color:oklch(0_0_0)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-inner-border-color:oklch(1_0_0/0.2)] dark:contrast-more:[--media-surface-inner-border-color:oklch(1_0_0/0.2)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:transparent] dark:contrast-more:[--media-surface-outer-border-color:transparent]">
<audio src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"></audio>
<media-error-dialog class="peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none">
<div class="absolute inset-0 z-20 flex items-center gap-3 rounded-full px-5 pr-0.5 bg-(--media-surface-background-color) text-(--media-text-color) backdrop-blur-lg backdrop-saturate-150 transition-[opacity,filter] ease-out duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-xs group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-xs group-data-ending-style/error:delay-0">
<div class="flex flex-1 items-center gap-2">
<media-alert-dialog-title class="font-semibold leading-tight">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="opacity-70 wrap-anywhere"></media-alert-dialog-description>
</div>
<div class="flex gap-2 *:flex-1">
<media-alert-dialog-close class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<div class="peer/controls @container/media-controls p-[0.375rem] flex items-center gap-x-[0.075rem] rounded-3xl text-shadow-2xs text-shadow-(color:--media-current-shadow-color) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 text-(--media-text-color) peer-data-open/error:**:invisible">
<media-tooltip-group>
<div class="flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5">
<span class="group/play inline-flex relative">
<media-buffering-indicator class="peer/play-buffering absolute inset-0 z-10 hidden place-content-center pointer-events-none text-inherit not-data-visible:[--media-spinner-animation:none] data-visible:grid">
<media-icon name="spinner" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
</media-buffering-indicator>
<media-play-button commandfor="play-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 group peer-data-visible/play-buffering:[&>svg]:opacity-0">
<media-icon name="restart" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-ended:block group-data-ended:opacity-100"></media-icon>
<media-icon name="play" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100"></media-icon>
<media-icon name="pause" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100"></media-icon>
</media-play-button>
<media-tooltip id="play-tooltip" side="top" boundary="viewport" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
</span>
<media-seek-button commandfor="seek-backward-tooltip" seconds="-10" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90">
<span class="relative grid">
<media-icon name="seek" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out [scale:-1_1]"></media-icon>
<span class="text-[10px] font-medium tracking-tighter tabular-nums absolute -left-px -bottom-0.75">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-backward-tooltip" side="top" boundary="viewport" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-forward-tooltip" seconds="10" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90">
<span class="relative grid">
<media-icon name="seek" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
<span class="text-[10px] font-medium tracking-tighter tabular-nums absolute -right-px -bottom-0.75">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-forward-tooltip" side="top" boundary="viewport" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] p-[0.1em] bg-current/30 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
</div>
<div class="@container/media-time flex items-center flex-1 gap-3 px-2">
<media-time type="current" class="hidden @2xs/media-time:block tabular-nums"></media-time>
<media-time-slider class="group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20">
<media-slider-track class="relative isolate overflow-hidden rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1 data-[orientation=vertical]:h-full bg-black/10 dark:bg-white/20 dark:ring-1 dark:ring-black/5">
<media-slider-fill class="absolute rounded-[inherit] pointer-events-none bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)"></media-slider-fill>
<media-slider-buffer class="absolute rounded-[inherit] pointer-events-none bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)"></media-slider-buffer>
</media-slider-track>
<media-slider-thumb class="z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.35),0_1px_2px_-1px_oklch(0_0_0/0.35)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/15 hover:outline-offset-0 focus-visible:outline-current/15 focus-visible:outline-offset-0 after:absolute after:-inset-1 after:rounded-[inherit] after:shadow-[0_0_0_2px_currentColor] after:pointer-events-none after:transition-[opacity,scale] after:duration-150 after:ease-out after:opacity-0 after:scale-50 focus-visible:after:opacity-100 focus-visible:after:scale-100 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))] size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3"></media-slider-thumb>
<media-slider-preview class="group/preview before:block before:min-w-1 before:h-1 before:bg-current before:rounded-full before:opacity-0 before:scale-50 before:shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_2px_0_oklch(0_0_0/0.35)] before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden">
<media-slider-value type="pointer" class="absolute bottom-9 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none"></media-slider-value>
</media-slider-preview>
</media-time-slider>
<media-time toggle type="remaining" class="tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2"></media-time>
</div>
<div class="flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5">
<media-playback-rate-button commandfor="playback-rate-menu" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 tabular-nums after:w-[4ch] after:content-[attr(data-rate)_'×'] data-[inline-rate-label]:after:content-none"></media-playback-rate-button>
<media-menu id="playback-rate-menu" side="top" align="center" boundary="viewport" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.25rem] [--menu-border-radius:0.75rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none overflow-auto!">
<media-playback-rate-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/65 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
</media-menu>
<media-mute-button commandfor="audio-volume-popover" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-9 aspect-square p-0 active:scale-90 group">
<media-icon name="volume-off" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-muted:block group-data-muted:opacity-100"></media-icon>
<media-icon name="volume-low" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100"></media-icon>
<media-icon name="volume-high" class="block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100"></media-icon>
</media-mute-button>
<media-popover id="audio-volume-popover" open-on-hover delay="200" close-delay="100" side="top" boundary="viewport" class="bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) py-3 px-0 rounded-full">
<media-volume-slider class="group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="relative isolate overflow-hidden rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1 data-[orientation=vertical]:h-full bg-black/10 dark:bg-white/20 dark:ring-1 dark:ring-black/5">
<media-slider-fill class="absolute rounded-[inherit] pointer-events-none bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.35),0_1px_2px_-1px_oklch(0_0_0/0.35)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/15 hover:outline-offset-0 focus-visible:outline-current/15 focus-visible:outline-offset-0 after:absolute after:-inset-1 after:rounded-[inherit] after:shadow-[0_0_0_2px_currentColor] after:pointer-events-none after:transition-[opacity,scale] after:duration-150 after:ease-out after:opacity-0 after:scale-50 focus-visible:after:opacity-100 focus-visible:after:scale-100 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))] size-3"></media-slider-thumb>
</media-volume-slider>
</media-popover>
</div>
</media-tooltip-group>
</div>
</media-container>
</audio-player>
Minimal Video Skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode, isValidElement } from 'react';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, QualityIcon, RestartIcon, SeekIcon, SpeechIcon, SpeedIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { createPlayer, Poster, Container, usePlayer, AirPlayButton, useAudioTrackOptions, BufferingIndicator, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, Popover, useQualityOptions, SeekButton, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider, type RenderProp } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './player.css';
const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;
const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;
function MenuChevron({ flipped = false }: { flipped?: boolean }): ReactNode {
return <ChevronIcon className={`media-icon media-menu__chevron ${flipped ? 'media-icon--flipped' : undefined}`} />;
}
function SettingsMenu(): ReactNode {
const playbackRate = usePlaybackRateOptions();
const quality = useQualityOptions();
const audioTrack = useAudioTrackOptions();
const captions = useCaptionsOptions();
const hasPlaybackRate = playbackRate?.state.availability === 'available';
const hasQuality = quality?.state.availability === 'available';
const hasAudioTrack = audioTrack?.state.availability === 'available';
const hasCaptions = captions?.state.availability === 'available';
if (!hasPlaybackRate && !hasQuality && !hasAudioTrack && !hasCaptions) return null;
return (
<Menu.Root side="top" align="center">
<Menu.Trigger aria-label="Settings" className="media-button--settings" render={<Button />}>
<GearIcon className="media-icon media-icon--settings" />
</Menu.Trigger>
<Menu.Content className="media-popover media-menu media-menu--settings">
<Menu.View className="media-menu__panel">
<div className="media-menu__group">
{hasQuality ? (
<Menu.Root>
<Menu.Trigger
type="quality"
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<QualityIcon className="media-icon" />
<span>Quality</span>
<span className="media-menu__hint">
<Menu.ItemValue className="media-menu__hint-label" />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Back className="media-menu__back">
<MenuChevron flipped />
Quality
</Menu.Back>
<Menu.Separator className="media-menu__separator" />
<Menu.RadioGroup
className="media-menu__group"
value={quality.value}
onValueChange={quality.setValue}
aria-label="Quality"
>
{quality.options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<span>
{option.label}
{option.tier ? <sup className="media-menu__tier">{option.tier}</sup> : null}
</span>
{option.badge ? <span className="media-badge">{option.badge}</span> : null}
<Menu.ItemIndicator
checked={option.value === quality.value}
forceMount
className="media-menu__indicator"
>
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasAudioTrack ? (
<Menu.Root>
<Menu.Trigger
type="audio-track"
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<SpeechIcon className="media-icon" />
<span>Audio</span>
<span className="media-menu__hint">
<Menu.ItemValue className="media-menu__hint-label" />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Back className="media-menu__back">
<MenuChevron flipped />
Audio
</Menu.Back>
<Menu.Separator className="media-menu__separator" />
<Menu.RadioGroup
className="media-menu__group"
value={audioTrack.value}
onValueChange={audioTrack.setValue}
aria-label="Audio tracks"
>
{audioTrack.options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === audioTrack.value}
forceMount
className="media-menu__indicator"
>
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasPlaybackRate ? (
<Menu.Root>
<Menu.Trigger
type="playback-rate"
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<SpeedIcon className="media-icon" />
<span>Speed</span>
<span className="media-menu__hint">
<Menu.ItemValue className="media-menu__hint-label" />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Back className="media-menu__back">
<MenuChevron flipped />
Speed
</Menu.Back>
<Menu.Separator className="media-menu__separator" />
<Menu.RadioGroup
className="media-menu__group"
value={playbackRate.value}
onValueChange={playbackRate.setValue}
aria-label="Playback rate"
>
{playbackRate.options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === playbackRate.value}
forceMount
className="media-menu__indicator"
>
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasCaptions ? (
<Menu.Root>
<Menu.Trigger
type="captions"
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<CaptionsOffIcon className="media-icon" />
<span>Captions</span>
<span className="media-menu__hint">
<Menu.ItemValue className="media-menu__hint-label" />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Back className="media-menu__back">
<MenuChevron flipped />
Captions
</Menu.Back>
<Menu.Separator className="media-menu__separator" />
<Menu.RadioGroup
className="media-menu__group"
value={captions.value}
onValueChange={captions.setValue}
aria-label="Captions"
>
{captions.options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === captions.value}
forceMount
className="media-menu__indicator"
>
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
</div>
</Menu.View>
</Menu.Content>
</Menu.Root>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
export const Player = createPlayer({ features: videoFeatures });
export interface VideoPlayerProps {
src: string;
style?: CSSProperties;
className?: string;
poster?: string | RenderProp<Poster.State> | undefined;
}
/**
* @example
* ```tsx
* <VideoPlayer
* src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
* poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp"
* />
* ```
*/
export function VideoPlayer({ src, className, poster, placeholder, style, ...rest }: VideoPlayerProps): ReactNode {
const containerStyle = placeholder
? ({ '--media-poster-placeholder': `url(${placeholder})`, ...style } as CSSProperties)
: style;
return (
<Player.Provider>
<Container
className={`media-minimal-skin media-minimal-skin--video ${className ?? ''}`}
style={containerStyle}
{...rest}
>
<Video src={src} playsInline />
{poster && (
<Poster src={isString(poster) ? poster : undefined} render={isRenderProp(poster) ? poster : undefined} />
)}
<BufferingIndicator
render={(props) => (
<div {...props} className="media-buffering-indicator">
<SpinnerIcon className="media-icon" />
</div>
)}
/>
<ErrorDialog.Root>
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title">Something went wrong.</ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--primary">OK</ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root className="media-controls">
<Tooltip.Provider>
<div className="media-button-group">
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PlayButton className="media-button--play" render={<Button />}>
<RestartIcon className="media-icon media-icon--restart" />
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</PlayButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek media-icon--flipped" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
</div>
<div className="media-time-controls">
<Time.Group className="media-time-group">
<Time.Value toggle type="current" className="media-time media-time--current" />
<Time.Separator className="media-time-separator" />
<Time.Value type="duration" className="media-time media-time--duration" />
</Time.Group>
<TimeSlider.Root className="media-slider">
<TimeSlider.Track className="media-slider__track">
<TimeSlider.Fill className="media-slider__fill" />
<TimeSlider.Buffer className="media-slider__buffer" />
</TimeSlider.Track>
<TimeSlider.Thumb className="media-slider__thumb" />
<div className="media-thumbnail media-slider__thumbnail">
<div className="media-thumbnail__image-wrapper">
<Slider.Thumbnail className="media-thumbnail__image" />
</div>
<TimeSlider.Value type="pointer" className="media-time media-thumbnail__time" />
<SpinnerIcon className="media-thumbnail__spinner media-icon" />
</div>
<TimeSlider.Preview className="media-slider__preview">
<TimeSlider.Value type="pointer" className="media-time media-slider__value" />
</TimeSlider.Preview>
</TimeSlider.Root>
</div>
<div className="media-button-group">
<VolumePopover />
<SettingsMenu />
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CastButton className="media-button--cast" render={<Button />}>
<CastEnterIcon className="media-icon media-icon--cast-enter" />
<CastExitIcon className="media-icon media-icon--cast-exit" />
</CastButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<AirPlayButton className="media-button--airplay" render={<Button />}>
<AirPlayEnterIcon className="media-icon media-icon--airplay-enter" />
<AirPlayExitIcon className="media-icon media-icon--airplay-exit" />
</AirPlayButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PiPButton className="media-button--pip" render={<Button />}>
<PipEnterIcon className="media-icon media-icon--pip-enter" />
<PipExitIcon className="media-icon media-icon--pip-exit" />
</PiPButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<FullscreenButton className="media-button--fullscreen" render={<Button />}>
<FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" />
<FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" />
</FullscreenButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
</div>
</Tooltip.Provider>
</Controls.Root>
<div className="media-overlay" />
{/* Hotkeys */}
<Hotkey keys="Space" action="togglePaused" />
<Hotkey keys="k" action="togglePaused" />
<Hotkey keys="m" action="toggleMuted" />
<Hotkey keys="f" action="toggleFullscreen" />
<Hotkey keys="c" action="toggleSubtitles" />
<Hotkey keys="i" action="togglePictureInPicture" />
<Hotkey keys="ArrowRight" action="seekStep" value={SEEK_TIME / 2} />
<Hotkey keys="ArrowLeft" action="seekStep" value={-(SEEK_TIME / 2)} />
<Hotkey keys="l" action="seekStep" value={SEEK_TIME} />
<Hotkey keys="j" action="seekStep" value={-SEEK_TIME} />
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
<Hotkey keys="0-9" action="seekToPercent" />
<Hotkey keys="Home" action="seekToPercent" value={0} />
<Hotkey keys="End" action="seekToPercent" value={100} />
<Hotkey keys=">" action="speedUp" />
<Hotkey keys="<" action="speedDown" />
{/* Gestures */}
<Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
<Gesture type="tap" action="toggleControls" pointer="touch" />
<Gesture type="doubletap" action="seekStep" value={-SEEK_TIME} region="left" />
<Gesture type="doubletap" action="toggleFullscreen" region="center" />
<Gesture type="doubletap" action="seekStep" value={SEEK_TIME} region="right" />
{/* Input Feedback */}
<StatusAnnouncer />
<div className="media-input-feedback">
<VolumeIndicator.Root className="media-input-feedback-island media-input-feedback-island--volume">
<VolumeIndicator.Fill className="media-input-feedback-island__content">
<VolumeHighIcon className="media-icon media-icon--volume-high" />
<VolumeLowIcon className="media-icon media-icon--volume-low" />
<VolumeOffIcon className="media-icon media-icon--volume-off" />
<div className="media-input-feedback-island__progress" aria-hidden="true" />
<VolumeIndicator.Value className="media-input-feedback-island__value" />
</VolumeIndicator.Fill>
</VolumeIndicator.Root>
<StatusIndicator.Root
actions={TOP_STATUS_ACTIONS}
className="media-input-feedback-island media-input-feedback-island--status"
>
<div className="media-input-feedback-island__content">
<CaptionsOnIcon className="media-icon media-icon--captions-on" />
<CaptionsOffIcon className="media-icon media-icon--captions-off" />
<FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" />
<FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" />
<PipEnterIcon className="media-icon media-icon--pip-enter" />
<PipExitIcon className="media-icon media-icon--pip-exit" />
<StatusIndicator.Value className="media-input-feedback-island__value" />
</div>
</StatusIndicator.Root>
<SeekIndicator.Root className="media-input-feedback-bubble">
<ChevronIcon className="media-icon media-icon--seek" />
<SeekIndicator.Value className="media-time" />
</SeekIndicator.Root>
<StatusIndicator.Root actions={CENTER_STATUS_ACTIONS} className="media-input-feedback-bubble">
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</StatusIndicator.Root>
</div>
</Container>
</Player.Provider>
);
}
// ================================================================
// Components
// ================================================================
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
return (
<button
ref={ref}
type="button"
className={`media-button media-button--subtle media-button--icon ${className ?? ''}`}
{...props}
/>
);
});
function VolumePopover(): ReactNode {
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
const muteButton = (
<MuteButton className="media-button--mute" render={<Button />}>
<VolumeOffIcon className="media-icon media-icon--volume-off" />
<VolumeLowIcon className="media-icon media-icon--volume-low" />
<VolumeHighIcon className="media-icon media-icon--volume-high" />
</MuteButton>
);
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top">
<Popover.Trigger render={muteButton} />
<Popover.Popup className="media-popover media-popover--volume">
<VolumeSlider.Root className="media-slider" orientation="vertical" thumbAlignment="edge">
<VolumeSlider.Track className="media-slider__track">
<VolumeSlider.Fill className="media-slider__fill" />
</VolumeSlider.Track>
<VolumeSlider.Thumb className="media-slider__thumb media-slider__thumb--persistent" />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Root>
);
}
// ================================================================
// Utilities
// ================================================================
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isRenderProp(value: unknown): value is RenderProp<unknown> {
return typeof value === 'function' || isValidElement(value);
}
/* ==========================================================================
Reset
========================================================================== */
.media-minimal-skin *,
.media-minimal-skin *::before,
.media-minimal-skin *::after {
box-sizing: border-box;
}
.media-minimal-skin img,
.media-minimal-skin video,
.media-minimal-skin svg {
display: block;
max-width: 100%;
}
.media-minimal-skin button {
font: inherit;
}
.media-minimal-skin [hidden][hidden] {
/* Keep authored templates hidden even when component classes set display. */
display: none;
}
@media (prefers-reduced-motion: no-preference) {
.media-minimal-skin {
interpolate-size: allow-keywords;
}
}
/* ==========================================================================
Root Container
========================================================================== */
.media-minimal-skin {
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
--media-icon-size: 18px;
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
position: relative;
display: block;
width: 100%;
height: 100%;
container: media-root / inline-size;
font-family:
Inter Variable,
Inter,
ui-sans-serif,
system-ui,
sans-serif;
font-size: 0.8125rem; /* 13px at 100% font size */
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
scrollbar-width: thin;
border-radius: var(--media-border-radius, 0.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-color-scrollbar-thumb);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
/* ==========================================================================
Media Element
========================================================================== */
.media-minimal-skin ::slotted(video),
.media-minimal-skin video {
display: block;
width: 100%;
height: 100%;
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
}
.media-minimal-skin ::slotted(video) {
border-radius: var(--media-video-border-radius);
}
.media-minimal-skin video {
border-radius: inherit;
}
.media-minimal-skin:fullscreen ::slotted(video),
.media-minimal-skin:fullscreen video {
object-fit: contain;
}
/* ==========================================================================
Overlay / Scrim
========================================================================== */
.media-minimal-skin .media-overlay {
position: absolute;
inset: 0;
pointer-events: none;
background-image: linear-gradient(to top, oklch(0 0 0 / 0.7), oklch(0 0 0 / 0.5) 7.5rem, oklch(0 0 0 / 0));
border-radius: inherit;
opacity: 0;
backdrop-filter: blur(0) saturate(1);
transition-timing-function: ease-out;
transition-duration: var(--media-controls-transition-duration);
transition-property: opacity, backdrop-filter;
}
.media-minimal-skin .media-error ~ .media-overlay {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-minimal-skin .media-controls[data-visible] ~ .media-overlay,
.media-minimal-skin .media-error[data-open] ~ .media-overlay {
opacity: 1;
}
.media-minimal-skin .media-buffering-indicator[data-visible] ~ .media-overlay {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-minimal-skin .media-error[data-open] ~ .media-overlay {
backdrop-filter: blur(16px) saturate(1.2);
}
/* ==========================================================================
Buffering Indicator
========================================================================== */
.media-minimal-skin .media-buffering-indicator {
position: absolute;
inset: 0;
z-index: 10;
display: none;
place-content: center;
color: oklch(1 0 0);
pointer-events: none;
&:not([data-visible]) {
--media-spinner-animation: none;
}
&[data-visible] {
display: grid;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-minimal-skin .media-error:not([data-open]) {
display: none;
}
.media-minimal-skin .media-error__title {
font-weight: 600;
line-height: 1.25;
}
.media-minimal-skin .media-error__description {
overflow-wrap: anywhere;
opacity: 0.7;
}
.media-minimal-skin .media-error__actions {
display: flex;
gap: 0.5rem;
& > * {
flex: 1;
}
}
.media-minimal-skin .media-error[data-open] ~ .media-controls * {
visibility: hidden;
}
/* ==========================================================================
Controls
========================================================================== */
.media-minimal-skin .media-controls {
display: flex;
align-items: center;
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
}
/* ==========================================================================
Time Controls & Display
========================================================================== */
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: 0.75rem;
align-items: center;
container: media-time-controls / inline-size;
}
.media-minimal-skin .media-time-group {
display: flex;
gap: 0.25rem;
align-items: center;
}
.media-minimal-skin .media-time {
font-variant-numeric: tabular-nums;
}
.media-minimal-skin .media-time[role="button"] {
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: 0.25rem;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: 1rem;
corner-shape: squircle;
}
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
.media-minimal-skin .media-time--current,
.media-minimal-skin .media-time-separator {
display: none;
}
@container media-root (width > 42rem) {
.media-minimal-skin .media-time-controls {
flex-direction: row;
}
.media-minimal-skin .media-time--duration,
.media-minimal-skin .media-time-separator {
color: oklch(from currentColor l c h / 0.6);
}
.media-minimal-skin .media-time--current,
.media-minimal-skin .media-time-separator {
display: inline;
}
}
/* ==========================================================================
Buttons
========================================================================== */
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
min-height: 0;
padding: 0.5rem 1rem;
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: 0.5rem;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:active {
scale: 0.98;
}
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: 1rem;
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: oklch(0 0 0);
text-shadow: none;
background: oklch(1 0 0);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
background: oklch(from currentColor l c h / 0.1);
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
width: 2.375rem;
aspect-ratio: 1;
padding: 0;
&:active {
scale: 0.9;
}
& .media-icon__container {
display: grid;
}
& .media-icon {
grid-area: 1 / 1;
transition-behavior: allow-discrete;
transition-property: display, opacity;
transition-duration: 150ms;
transition-timing-function: ease-out;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
& .media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 10px; /* Hard coded due to size limitations. */
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
}
&:has(.media-icon--flipped) .media-icon__label {
right: unset;
left: -1px;
}
}
/* Playback rate button */
.media-minimal-skin .media-button--playback-rate {
padding: 0;
font-variant-numeric: tabular-nums;
&::after {
width: 4ch;
content: attr(data-rate) "\00D7";
}
&[data-inline-rate-label]::after {
content: none;
}
}
/* Settings button */
.media-minimal-skin .media-button--settings {
display: none;
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
&[aria-expanded="true"] .media-icon--settings {
transform: rotate(90deg);
}
}
.media-minimal-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
display: grid;
}
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
text content. */
.media-minimal-skin .media-button--live {
display: inline-flex;
gap: 0.4rem;
align-items: center;
width: auto;
aspect-ratio: auto;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
transition: background-color 150ms ease-out;
}
&[data-live-edge]::before {
background-color: oklch(0.65 0.22 27);
}
}
/* ==========================================================================
Button Groups
========================================================================== */
.media-minimal-skin .media-button-group {
display: flex;
gap: 0.075rem;
align-items: center;
@container media-root (width > 42rem) {
gap: 0.125rem;
}
}
/* ==========================================================================
Badge
========================================================================== */
.media-minimal-skin .media-badge {
padding: 0.25rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
border-radius: 0.25rem;
}
/* ==========================================================================
Icons
========================================================================== */
.media-minimal-skin .media-icon__container {
position: relative;
}
.media-minimal-skin .media-icon {
flex-shrink: 0;
width: var(--media-icon-size);
height: var(--media-icon-size);
}
.media-minimal-skin .media-icon--flipped {
scale: -1 1;
}
/* ==========================================================================
Poster Image
========================================================================== */
.media-minimal-skin media-poster,
.media-minimal-skin > img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
transition: opacity 0.25s;
}
.media-minimal-skin media-poster:not([data-visible]),
.media-minimal-skin > img:not([data-visible]),
.media-minimal-skin > img[data-visible]:not([data-loaded]) {
opacity: 0;
}
.media-minimal-skin media-poster ::slotted(img),
.media-minimal-skin media-poster img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: var(--media-video-border-radius);
}
.media-minimal-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
/* Blurred placeholder: HTML path targets media-poster::before, React path targets the skin container::before */
.media-minimal-skin media-poster::before,
.media-minimal-skin::before {
position: absolute;
inset: 0;
pointer-events: none;
content: "";
background-image: var(--media-poster-placeholder, none);
background-repeat: no-repeat;
background-position: var(--media-object-position, center);
background-size: var(--media-object-fit, contain);
filter: blur(var(--media-poster-placeholder-blur, 20px));
}
/* React path: hidden by default, transitions in while poster loads */
.media-minimal-skin::before {
opacity: 0;
transition: opacity 0.25s;
}
/* Show placeholder while the poster is visible but not yet loaded */
.media-minimal-skin:has(img[data-visible]:not([data-loaded]))::before {
opacity: 1;
}
.media-minimal-skin:fullscreen media-poster ::slotted(img),
.media-minimal-skin:fullscreen media-poster img,
.media-minimal-skin:fullscreen > img {
object-fit: contain;
}
/* ==========================================================================
Media thumbnail
========================================================================== */
.media-minimal-skin .media-thumbnail {
pointer-events: none;
& .media-thumbnail__image-wrapper {
position: relative;
background-color: oklch(0 0 0 / 0.9);
border-radius: 0.5rem;
}
& .media-thumbnail__image {
display: block;
border-radius: inherit;
}
& .media-thumbnail__time {
display: block;
margin-top: 0.5rem;
text-align: center;
}
& .media-overlay {
opacity: 1;
}
& .media-thumbnail__spinner {
position: absolute;
top: 50%;
left: 50%;
opacity: 0;
translate: -50% -50%;
}
& .media-thumbnail__image,
& .media-thumbnail__spinner {
transition: opacity 150ms ease-out;
}
&:not(:has(.media-thumbnail__image[data-loading])) {
& .media-thumbnail__spinner {
--media-spinner-animation: none;
}
}
&:has(.media-thumbnail__image[data-loading]) {
& .media-thumbnail__image {
opacity: 0;
}
& .media-thumbnail__spinner {
opacity: 1;
}
}
}
/* ==========================================================================
Slider
========================================================================== */
.media-minimal-skin .media-slider {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: calc(infinity * 1px);
&[data-orientation="horizontal"] {
width: 100%;
min-width: 5rem;
height: 2rem;
}
&[data-orientation="vertical"] {
width: 2rem;
height: 4.5rem;
}
}
/* Track */
.media-minimal-skin .media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: inherit;
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: 0.1875rem;
}
&[data-orientation="vertical"] {
width: 0.1875rem;
height: 100%;
}
}
/* Thumb */
.media-minimal-skin .media-slider__thumb {
position: absolute;
z-index: 10;
width: 0.75rem;
height: 0.75rem;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
background-color: currentColor;
border-radius: calc(infinity * 1px);
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
0 1px 3px 0 oklch(0 0 0 / 0.15),
0 1px 2px -1px oklch(0 0 0 / 0.15);
opacity: 0;
transform-origin: center;
scale: 0.7;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale, outline-offset;
&[data-orientation="horizontal"] {
top: 50%;
left: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-fill));
left: 50%;
}
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
.media-minimal-skin .media-slider:hover .media-slider__thumb,
.media-minimal-skin .media-slider:focus-within .media-slider__thumb,
.media-minimal-skin .media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
/* Preview */
.media-minimal-skin .media-slider__preview {
& .media-slider__value,
&::before {
opacity: 0;
scale: 0.5;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
& .media-slider__value {
position: absolute;
bottom: 1.5rem;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
filter: blur(8px);
translate: -50% 0.5rem;
transition-property: filter, opacity, scale, translate;
}
&::before {
display: block;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
}
&[data-pointing] .media-slider__value,
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
&[data-pointing] .media-slider__value {
filter: blur(0);
translate: -50% 0;
}
&[data-orientation="horizontal"]::before {
min-width: 1px;
height: 1.25rem;
}
&[data-orientation="vertical"]::before {
width: 1.25rem;
min-height: 1px;
}
}
/* Shared track fills */
.media-minimal-skin .media-slider__buffer,
.media-minimal-skin .media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
}
.media-minimal-skin .media-slider__buffer[data-orientation="horizontal"],
.media-minimal-skin .media-slider__fill[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
}
.media-minimal-skin .media-slider__buffer[data-orientation="vertical"],
.media-minimal-skin .media-slider__fill[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
}
/* Buffer */
.media-minimal-skin .media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
transition-timing-function: ease-out;
transition-duration: 0.25s;
&[data-orientation="horizontal"] {
width: var(--media-slider-buffer);
transition-property: width;
}
&[data-orientation="vertical"] {
height: var(--media-slider-buffer);
transition-property: height;
}
}
/* Fill */
.media-minimal-skin .media-slider__fill {
background-color: currentColor;
&[data-orientation="horizontal"] {
width: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
height: var(--media-slider-fill);
}
}
/* Dragging — thumb and fill follow the pointer position */
.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
width: var(--media-slider-pointer);
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
height: var(--media-slider-pointer);
}
/* ==========================================================================
Popups & Animations
========================================================================== */
.media-minimal-skin {
--media-popup-transition:
opacity var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
filter var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
transform var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
scale var(--media-popup-transition-timing-function) var(--media-popup-transition-duration);
}
.media-minimal-skin .media-popover,
.media-minimal-skin .media-tooltip {
--popup-translate-distance: 0.5rem;
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
filter: blur(4px);
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--popup-translate-x-distance, 0), var(--popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--popup-translate-y-distance: var(--popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--popup-translate-y-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--popup-translate-x-distance: var(--popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--popup-translate-x-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: left;
}
/* Safe area between trigger and popup */
&::before {
position: absolute;
pointer-events: inherit;
content: "";
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
inset-inline: 0;
width: 100%;
}
&[data-side="top"]::before {
top: 100%;
}
&[data-side="bottom"]::before {
bottom: 100%;
}
&[data-side="left"]::before,
&[data-side="right"]::before {
inset-block: 0;
height: 100%;
}
&[data-side="left"]::before {
left: 100%;
}
&[data-side="right"]::before {
right: 100%;
}
}
.media-minimal-skin .media-popover {
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-popover-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-popover-side-offset);
}
}
.media-minimal-skin .media-tooltip {
padding: 0.25rem 0.5rem;
font-size: 0.75rem; /* 12px at 100% font size */
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: 0.5rem;
box-shadow:
0 0 0 1px var(--media-tooltip-border-color),
0 4px 6px -1px oklch(0 0 0 / 0.2),
0 2px 4px -2px oklch(0 0 0 / 0.2);
backdrop-filter: var(--media-tooltip-backdrop-filter);
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
column-gap: 0.25rem;
align-items: center;
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-tooltip-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-tooltip-side-offset);
}
& .media-tooltip__kbd {
min-width: 1.5em;
padding: 0.1em;
margin-right: -0.25rem;
font-family: inherit;
font-size: 90%;
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: 0.25rem;
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
/* ==========================================================================
Menus
Note: Menus use `.media-popover` styles for positioning and transitions.
========================================================================== */
.media-minimal-skin .media-menu {
--menu-transition-duration: 250ms;
--menu-max-height: 14rem;
--menu-padding: 0.5rem;
--menu-border-radius: 1rem;
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--menu-border-radius);
box-shadow:
0 0 0 1px var(--media-popover-border-color),
0 4px 6px -1px oklch(0 0 0 / 0.1),
0 2px 4px -2px oklch(0 0 0 / 0.1);
backdrop-filter: var(--media-popover-backdrop-filter);
@media (prefers-reduced-motion: reduce) {
--menu-transition-duration: 0ms;
}
& .media-menu__panel {
position: absolute;
inset: 0;
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-in-out;
transition-duration: var(--menu-transition-duration);
transition-property: translate, filter;
will-change: translate, filter;
&[data-starting-style],
&[data-ending-style] {
overflow: hidden;
}
/* Root settings view — slides out when a submenu is active */
&[data-menu-root-view][data-menu-view-state="inactive"] {
filter: blur(8px);
translate: -100% 0;
/* Hide the indicator while transitioning */
& .media-menu__group::before {
display: none;
}
}
/* Submenu panels — slide in/out alongside the root view */
&[data-submenu] {
z-index: 10;
&:not([data-open], [data-ending-style]) {
translate: -100% 0;
transition-property: none;
}
&[data-starting-style],
&[data-ending-style] {
pointer-events: none;
filter: blur(8px);
}
&[data-starting-style][data-direction="forward"],
&[data-ending-style][data-direction="back"] {
translate: 100% 0;
}
&[data-ending-style][data-direction="forward"],
&[data-starting-style][data-direction="back"] {
translate: -100% 0;
}
}
}
& .media-menu__separator {
margin: 0.25rem 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
& .media-menu__group {
display: flex;
flex-direction: column;
gap: 0.125rem;
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --media-menu-item-highlight-anchor;
inset: anchor(inside);
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.1);
border-radius: var(--menu-item-border-radius);
transition: inset ease-in-out 100ms;
}
}
}
& .media-menu__item,
& .media-menu__back {
display: flex;
gap: 0.375rem;
align-items: center;
padding: 0.375rem 0.5rem;
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-out;
transition-duration: 100ms;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
@supports (top: anchor(top)) {
anchor-name: --media-menu-item-highlight-anchor;
background-color: transparent;
}
}
& .media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__indicator {
flex-shrink: 0;
margin-right: -0.25rem;
margin-left: auto;
opacity: 0;
}
& .media-menu__item {
justify-content: space-between;
font-variant-numeric: tabular-nums;
color: inherit;
&[aria-disabled="true"] {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
&[aria-checked="true"] .media-menu__indicator {
opacity: 1;
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
& .media-menu__tier {
padding-top: 1px;
padding-left: 0.125rem;
font-size: 0.5625rem;
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
& .media-menu__back {
width: 100%;
margin-bottom: 0.125rem;
}
& .media-menu__hint {
display: inline-flex;
gap: 0.25rem;
align-items: center;
min-width: 0;
padding-left: 0.5rem;
margin-left: auto;
font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
max-width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
width: 0.875rem;
height: 0.875rem;
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: 12rem;
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
transition:
var(--media-popup-transition),
height var(--media-popup-transition-timing-function) var(--menu-transition-duration),
width var(--media-popup-transition-timing-function) var(--menu-transition-duration);
/* Don't transition width and height on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
/* ==========================================================================
Native Caption Track
========================================================================== */
.media-minimal-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: -0.5rem;
&:has(.media-controls[data-visible]) {
--media-caption-track-y: -5rem;
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
--media-caption-track-y: -3rem;
}
}
}
.media-minimal-skin video::-webkit-media-text-track-container {
z-index: 1;
font-family: inherit;
scale: 0.98;
translate: 0 var(--media-caption-track-y);
transition: translate var(--media-caption-track-duration) ease-out;
transition-delay: var(--media-caption-track-delay);
}
/* ==========================================================================
Input Feedback
========================================================================== */
.media-minimal-skin .media-input-feedback {
position: absolute;
inset-inline: 0;
top: 0;
bottom: 3.5rem; /* Shift up a little in smaller containers */
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
overflow: hidden;
color: var(--media-color-primary, oklch(1 0 0));
pointer-events: none;
border-radius: inherit;
@container media-root (width > 24rem) {
bottom: 0;
}
}
/* --- Feedback islands ------------------------------------------------------- */
.media-minimal-skin .media-input-feedback-island {
position: absolute;
inset-inline: 0;
top: 0;
display: flex;
justify-content: center;
padding-top: 0.75rem;
padding-bottom: 8rem;
color: inherit;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
pointer-events: none;
background-image: linear-gradient(to bottom, oklch(0 0 0 / 0.35), oklch(0 0 0 / 0.2) 3rem, oklch(0 0 0 / 0));
transform-origin: top center;
transition-timing-function: ease-out;
transition-duration: 100ms;
.media-input-feedback-island__content {
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: space-between;
padding: 0.25rem 0.625rem;
}
.media-icon {
display: none;
flex-shrink: 0;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
.media-input-feedback-island__value {
margin-left: auto;
}
@media (pointer: fine) {
transition-property: translate, filter, opacity;
will-change: translate, filter, opacity;
}
@media (pointer: coarse) {
transition-property: translate, opacity;
will-change: translate, opacity;
}
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
transition-property: translate, filter, opacity;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
.media-input-feedback-island__content {
background: var(--media-controls-background-color);
border-radius: 0.5rem;
}
}
/* Default hidden state */
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
transition-timing-function: ease-in;
transition-duration: 400ms;
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: no-preference) {
&[data-ending-style] {
translate: 0 -100%;
}
}
}
}
.media-minimal-skin .media-input-feedback-island--volume {
.media-input-feedback-island__content {
width: min(80%, 14rem);
}
.media-input-feedback-island__progress {
--media-progress-fill: var(--media-volume-fill);
width: 100%;
height: 0.1875rem;
background-image: linear-gradient(
to right,
currentColor 0%,
currentColor var(--media-progress-fill),
oklch(from currentColor l c h / 0.2) var(--media-progress-fill),
oklch(from currentColor l c h / 0.2) 100%
);
border-radius: calc(Infinity * 1px);
box-shadow: 0 1px 0 var(--media-current-shadow-color-subtle);
}
&[data-level="high"] .media-icon--volume-high,
&[data-level="low"] .media-icon--volume-low,
&[data-level="off"] .media-icon--volume-off {
display: block;
}
@media (prefers-reduced-motion: no-preference) {
&[data-min] .media-input-feedback-island__content,
&[data-max] .media-input-feedback-island__content {
animation: media-shake 300ms ease-in-out;
}
}
}
.media-minimal-skin .media-input-feedback-island--status {
&[data-status="captions-on"] .media-icon--captions-on,
&[data-status="captions-off"] .media-icon--captions-off,
&[data-status="fullscreen"] .media-icon--fullscreen-enter,
&[data-status="exit-fullscreen"] .media-icon--fullscreen-exit,
&[data-status="pip"] .media-icon--pip-enter,
&[data-status="exit-pip"] .media-icon--pip-exit {
display: block;
}
}
/* --- Bubble ---------------------------------------------------------------- */
.media-minimal-skin .media-input-feedback-bubble {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: 1rem;
text-align: center;
/* Central bubble (play, pause) */
&:not([data-direction]) {
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
scale: 0.85;
}
&[data-ending-style] {
transition-timing-function: ease-in;
transition-duration: 100ms;
}
@media (prefers-reduced-motion: reduce) {
transition-duration: 50ms;
transition-property: opacity;
}
}
/* Directional bubbles (seek) */
&[data-direction] {
gap: 0.25rem;
@container media-root (width > 24rem) {
padding: 1.5rem;
}
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
/* Icons */
.media-icon {
display: none;
width: calc(var(--media-icon-size) * 2);
height: calc(var(--media-icon-size) * 2);
}
.media-icon--seek {
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[data-direction] .media-icon--seek,
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
display: block;
}
&[data-direction="backward"] .media-icon--seek {
scale: -1 1;
}
@media (prefers-reduced-motion: no-preference) {
.media-icon--seek {
transition-timing-function: ease-in-out;
transition-duration: 200ms;
transition-property: translate, opacity;
}
&[data-starting-style] .media-icon--seek,
&[data-ending-style] .media-icon--seek {
opacity: 0;
}
&[data-direction="forward"][data-starting-style] .media-icon--seek {
translate: -60% 0;
}
&[data-direction="backward"][data-starting-style] .media-icon--seek {
translate: 60% 0;
}
}
}
/* ==========================================================================
Icon State Visibility for Video Skins
Data-attribute-driven visibility rules for multi-state icon buttons.
Uses :is() with both element selectors (for HTML custom element wrappers)
and class selectors (for React rendered SVG elements).
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon--restart,
.media-button--play .media-icon--play,
.media-button--play .media-icon--pause,
.media-button--mute .media-icon--volume-off,
.media-button--mute .media-icon--volume-low,
.media-button--mute .media-icon--volume-high,
.media-button--fullscreen .media-icon--fullscreen-enter,
.media-button--fullscreen .media-icon--fullscreen-exit,
.media-button--pip .media-icon--pip-enter,
.media-button--pip .media-icon--pip-exit,
.media-button--cast .media-icon--cast-enter,
.media-button--cast .media-icon--cast-exit,
.media-button--airplay .media-icon--airplay-enter,
.media-button--airplay .media-icon--airplay-exit,
.media-button--captions .media-icon--captions-off,
.media-button--captions .media-icon--captions-on {
display: none;
opacity: 0;
}
/* --- Active icon per state --- */
/* Play: ended → restart */
.media-button--play[data-ended] .media-icon--restart,
/* Play: paused or not yet started (not ended) → play */
.media-button--play:not([data-ended])[data-paused] .media-icon--play,
.media-button--play:not([data-ended]):not([data-started]) .media-icon--play,
/* Play: started and not paused/ended → pause */
.media-button--play[data-started]:not([data-paused]):not([data-ended]) .media-icon--pause,
/* Mute: muted → volume off */
.media-button--mute[data-muted] .media-icon--volume-off,
/* Mute: volume low (not muted) → volume low */
.media-button--mute:not([data-muted])[data-volume-level="low"] .media-icon--volume-low,
/* Mute: volume high (not muted, not low) → volume high */
.media-button--mute:not([data-muted]):not([data-volume-level="low"]) .media-icon--volume-high,
/* Fullscreen: not fullscreen → enter */
.media-button--fullscreen:not([data-fullscreen]) .media-icon--fullscreen-enter,
/* Fullscreen: fullscreen → exit */
.media-button--fullscreen[data-fullscreen] .media-icon--fullscreen-exit,
/* Picture-in-Picture: not active → enter */
.media-button--pip:not([data-pip]) .media-icon--pip-enter,
/* Picture-in-Picture: active → exit */
.media-button--pip[data-pip] .media-icon--pip-exit,
/* Cast: not connected → enter */
.media-button--cast:not([data-cast-state="connected"]) .media-icon--cast-enter,
/* Cast: connected → exit */
.media-button--cast[data-cast-state="connected"] .media-icon--cast-exit,
/* AirPlay: not connected → enter */
.media-button--airplay:not([data-airplay-state="connected"]) .media-icon--airplay-enter,
/* AirPlay: connected → exit */
.media-button--airplay[data-airplay-state="connected"] .media-icon--airplay-exit,
/* Captions: not active → captions off */
.media-button--captions:not([data-active]) .media-icon--captions-off,
/* Captions: active → captions on */
.media-button--captions[data-active] .media-icon--captions-on {
display: block;
opacity: 1;
}
/* --- Pause keyframe animations on inactive icons --- */
/* The airplay-exit SVG defines its keyframes against CSS variables (mirroring
the spinner pattern). When the AirPlay session isn't active the SVG is
still in the DOM — just `display: none` — so its animations would keep
running. Set the variables to `none` to short-circuit the keyframes. */
.media-button--airplay:not([data-airplay-state="connected"]) {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
/* -------------------------------------------------------------------------- */
/* Global @keyframes for all video skins (CSS & Tailwind) */
/* -------------------------------------------------------------------------- */
@keyframes media-shake {
0%,
100% {
translate: 0 0;
}
20% {
translate: -6px 0;
}
40% {
translate: 4px 0;
}
60% {
translate: -2px 0;
}
80% {
translate: 1px 0;
}
}
/* -------------------------------------------------------------------------- */
/* Global @properties for all video skins (CSS & Tailwind) */
/* -------------------------------------------------------------------------- */
@property --media-progress-fill {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
/* ==========================================================================
Root
========================================================================== */
.media-minimal-skin--video {
--media-border-color: oklch(0 0 0 / 0.15);
--media-video-border-radius: var(--media-border-radius, 0.75rem);
--media-controls-background-color: transparent;
--media-controls-transition-duration: 100ms;
--media-controls-transition-timing-function: ease-out;
--media-error-dialog-transition-duration: 150ms;
--media-error-dialog-transition-delay: 100ms;
--media-error-dialog-transition-timing-function: ease-out;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
--media-tooltip-background-color: oklch(0 0 0 / 0.5);
--media-tooltip-border-color: oklch(1 0 0 / 0.1);
--media-tooltip-backdrop-filter: blur(16px) saturate(1.5);
--media-tooltip-text-color: currentColor;
--media-tooltip-side-offset: 0.5rem;
--media-tooltip-boundary-offset: 0.5rem;
--media-popover-background-color: var(--media-tooltip-background-color);
--media-popover-border-color: var(--media-tooltip-border-color);
--media-popover-backdrop-filter: var(--media-tooltip-backdrop-filter);
--media-popover-side-offset: 1.5rem;
--media-popover-boundary-offset: var(--media-tooltip-boundary-offset);
overflow: clip;
background: oklch(0 0 0);
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-color-scheme: dark) {
--media-border-color: oklch(1 0 0 / 0.15);
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-controls-background-color: oklch(0 0 0);
--media-tooltip-background-color: oklch(0 0 0);
}
@container media-root (width > 42rem) {
& > * {
--media-popover-side-offset: 0.5rem;
}
}
&:has(.media-controls:not([data-visible])) {
/* Slight delay to hide controls on non-touch devices after interaction */
@media (pointer: fine) {
--media-controls-transition-duration: 300ms;
}
@media (pointer: coarse) {
--media-controls-transition-duration: 150ms;
}
@media (prefers-reduced-motion: reduce) {
--media-controls-transition-duration: 50ms;
}
}
/* Inner border ring */
&::after {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
content: "";
border-radius: inherit;
box-shadow: inset 0 0 0 1px var(--media-border-color);
}
/* Fullscreen */
&:fullscreen {
--media-border-radius: 0;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-minimal-skin--video .media-error {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
outline: none;
}
.media-minimal-skin--video .media-error__dialog {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 16rem;
padding: 1rem;
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.5);
pointer-events: auto;
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: var(--media-error-dialog-transition-timing-function);
transition-duration: var(--media-error-dialog-transition-duration);
transition-property: opacity, scale;
}
.media-minimal-skin--video .media-error[data-starting-style] .media-error__dialog,
.media-minimal-skin--video .media-error[data-ending-style] .media-error__dialog {
opacity: 0;
scale: 0.5;
}
.media-minimal-skin--video .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
.media-minimal-skin--video .media-error__content {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.375rem 0;
}
.media-minimal-skin--video .media-error__title {
font-size: 1.125rem;
}
.media-minimal-skin--video .media-error[data-open] ~ .media-controls {
display: none;
}
/* ==========================================================================
Controls (hide/show behavior)
========================================================================== */
.media-minimal-skin--video .media-controls {
position: absolute;
inset-inline: 0.25rem;
bottom: 0.25rem;
z-index: 10;
flex-wrap: wrap;
column-gap: 0.5rem;
padding: 0.25rem;
color: oklch(1 0 0);
border-radius: 0.75rem;
transition-timing-function: var(--media-controls-transition-timing-function);
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) {
transition-property: translate, filter, opacity;
will-change: translate, filter, opacity;
}
@media (pointer: coarse) {
transition-property: translate, opacity;
will-change: translate, opacity;
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 100%;
}
}
& .media-time-controls {
flex: 0 0 100%;
order: -1;
padding-inline: 0.625rem;
}
& .media-button-group:first-child {
flex: 1;
text-align: left;
}
& .media-button-group:last-child {
flex: 1;
justify-content: end;
}
@container media-root (width > 42rem) {
inset-inline: 0.5rem;
bottom: 0.5rem;
flex-wrap: nowrap;
& .media-time-controls {
flex: 1;
order: unset;
}
& .media-button-group:first-child,
& .media-button-group:last-child {
flex: 0 0 auto;
}
}
}
/* Hide cursor when controls are hidden */
.media-minimal-skin--video:has(.media-controls:not([data-visible])) {
cursor: none;
}
/* ==========================================================================
Sliders
========================================================================== */
.media-minimal-skin--video .media-slider__track {
box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);
}
/* ==========================================================================
Popups & Animations
========================================================================== */
.media-minimal-skin--video .media-popover--volume {
padding-block: 0.75rem;
background: transparent;
border-radius: 0.75rem;
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
background: var(--media-controls-background-color);
}
@container media-root (width > 42rem) {
& > * {
--media-popover-side-offset: 0rem;
}
}
}
/* ==========================================================================
Slider preview
========================================================================== */
.media-minimal-skin--video .media-slider__thumbnail {
--media-slider-thumbnail-max-width: 11rem;
--media-slider-thumbnail-max-height: 8rem;
--media-slider-thumbnail-padding: -0.5rem;
/**
Inset is the difference between the container width and the slider (100%) width.
We only add to the end as we render the time there.
*/
--media-slider-thumbnail-inset: calc(100cqi - 100%);
position: absolute;
bottom: 100%;
left: clamp(
calc(var(--media-slider-thumbnail-max-width) / 2 + var(--media-slider-thumbnail-padding)),
var(--media-slider-pointer),
calc(
100% -
var(--media-slider-thumbnail-max-width) /
2 -
var(--media-slider-thumbnail-padding) +
var(--media-slider-thumbnail-inset)
)
);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: scale, opacity, filter;
@container media-root (width > 42rem) {
left: var(--media-slider-pointer);
}
& .media-thumbnail__image-wrapper {
position: relative;
&::after {
position: absolute;
inset: 0;
content: "";
border-radius: inherit;
box-shadow:
0 0 0 1px oklch(0 0 0 / 0.05),
0 1px 3px 0 oklch(0 0 0 / 0.2),
0 1px 2px -1px oklch(0 0 0 / 0.2);
}
}
& .media-thumbnail__image {
max-width: var(--media-slider-thumbnail-max-width);
max-height: var(--media-slider-thumbnail-max-height);
}
}
.media-minimal-skin--video .media-slider[data-pointing] .media-slider__thumbnail:has([role="img"]:not([data-hidden])) {
opacity: 1;
filter: blur(0);
scale: 1;
}
.media-minimal-skin--video
.media-slider__thumbnail:has([role="img"]:not([data-hidden]))
+ .media-slider__preview
.media-slider__value {
display: none;
}
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode, isValidElement } from 'react';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, QualityIcon, RestartIcon, SeekIcon, SpeechIcon, SpeedIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { createPlayer, Poster, Container, usePlayer, AirPlayButton, useAudioTrackOptions, BufferingIndicator, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, Popover, useQualityOptions, SeekButton, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider, type RenderProp } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './player.css';
const badge = "rounded-full bg-current/10 px-1.5 py-0.5 text-[0.6875rem] font-medium leading-none text-current/70";
const bufferingIndicator = "peer/buffering absolute inset-0 z-10 hidden place-content-center pointer-events-none text-white not-data-visible:[--media-spinner-animation:none] data-visible:grid";
const button = {
base: "flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden",
primary: "bg-white text-black font-medium text-shadow-none",
subtle: "bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10",
icon: "grid w-[2.375rem] aspect-square p-0 active:scale-90",
live: "inline-flex items-center gap-1.5 aspect-auto w-auto px-3 py-2 text-xs font-semibold uppercase tracking-wider leading-none before:inline-block before:size-2 before:shrink-0 before:rounded-full before:bg-current/40 before:transition-colors before:duration-150 before:ease-out before:content-[\"\"] data-[live-edge]:before:bg-red-500",
};
const buttonGroupEnd = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 justify-end @2xl/media-root:flex-none";
const buttonGroupStart = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 @2xl/media-root:flex-none";
const controls = "peer/controls @container/media-controls flex items-center bg-(--media-controls-background-color) [backdrop-filter:var(--media-controls-backdrop-filter)] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) absolute bottom-1 inset-x-1 p-1 gap-x-2 flex-wrap rounded-xl text-white z-10 peer-data-open/error:hidden ease-(--media-controls-transition-timing-function) duration-(--media-controls-transition-duration) pointer-fine:will-change-[translate,filter,opacity] pointer-fine:transition-[translate,filter,opacity] pointer-coarse:will-change-[translate,opacity] pointer-coarse:transition-[translate,opacity] not-data-visible:opacity-0 not-data-visible:pointer-events-none motion-safe:not-data-visible:translate-y-full pointer-fine:motion-safe:not-data-visible:blur-sm @2xl/media-root:flex-nowrap @2xl/media-root:bottom-2 @2xl/media-root:inset-x-2 @2xl/media-root:*:[--media-popover-side-offset:0rem]";
const error = {
root: "peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none pointer-events-none outline-none",
dialog: "flex flex-col gap-3 max-w-64 p-4 text-white text-shadow-2xs text-shadow-black/50 transition-[opacity,scale,transform] duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) ease-(--media-error-dialog-transition-timing-function) group-data-starting-style/error:opacity-0 group-data-starting-style/error:scale-50 group-data-ending-style/error:opacity-0 group-data-ending-style/error:scale-50 group-data-ending-style/error:delay-0 pointer-events-auto",
content: "flex flex-col gap-2 py-1.5",
title: "font-semibold leading-tight text-lg",
description: "opacity-70 wrap-anywhere",
actions: "flex gap-2 *:flex-1",
};
const icon = "block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out";
const iconContainer = "relative grid";
const iconFlipped = "[scale:-1_1]";
const iconState = {
play: {
button: "group",
restart: "hidden opacity-0 group-data-ended:block group-data-ended:opacity-100",
play: "hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100",
pause: "hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100",
},
mute: {
button: "group",
volumeOff: "hidden opacity-0 group-data-muted:block group-data-muted:opacity-100",
volumeLow: "hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100",
volumeHigh: "hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100",
},
fullscreen: {
button: "group",
enter: "hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100",
exit: "hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100",
},
captions: {
button: "group",
off: "hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100",
on: "hidden opacity-0 group-data-active:block group-data-active:opacity-100",
},
pip: {
button: "group",
off: "hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100",
on: "hidden opacity-0 group-data-pip:block group-data-pip:opacity-100",
},
cast: {
button: "group",
enter: "hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100",
exit: "hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100",
},
airplay: {
button: "group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]",
enter: "hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100",
exit: "hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100",
},
};
const inputFeedback = {
root: "absolute inset-x-0 top-0 bottom-14 pointer-events-none grid grid-cols-3 items-center justify-items-center overflow-hidden rounded-[inherit] @2xl/media-root:bottom-0 [color:var(--media-color-primary,oklch(1_0_0))]",
island: {
base: "group/input-indicator absolute top-0 inset-x-0 pt-3 pb-32 flex justify-center text-inherit font-medium origin-top pointer-events-none duration-100 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-400 data-starting-style:ease-in data-ending-style:duration-400 data-ending-style:ease-in [background-image:linear-gradient(to_bottom,oklch(0_0_0/0.35),oklch(0_0_0/0.2)_3rem,oklch(0_0_0/0))] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) pointer-fine:will-change-[translate,filter,opacity] pointer-fine:transition-[translate,filter,opacity] pointer-coarse:will-change-[translate,opacity] pointer-coarse:transition-[translate,opacity] pointer-fine:motion-safe:data-starting-style:blur-sm pointer-fine:motion-safe:data-ending-style:blur-sm motion-safe:data-ending-style:-translate-y-full",
content: "flex justify-between items-center gap-2 px-2.5 py-1 *:last:ml-auto [@media(prefers-reduced-transparency:reduce)]:bg-(--media-controls-background-color) [@media(prefers-reduced-transparency:reduce)]:rounded-lg contrast-more:bg-(--media-controls-background-color) contrast-more:rounded-lg",
volume: "*:data-feedback-island-content:w-[min(80%,14rem)]",
volumeProgress: "[--media-progress-fill:var(--media-volume-fill)] w-full h-0.75 rounded-full [background-image:linear-gradient(to_right,currentColor_0%,currentColor_var(--media-progress-fill),oklch(from_currentColor_l_c_h/0.2)_var(--media-progress-fill),oklch(from_currentColor_l_c_h/0.2)_100%)] shadow-[0_1px_0_var(--media-current-shadow-color-subtle)]",
shownVolume: "data-open:duration-100 data-min:*:data-feedback-island-content:animate-media-shake data-max:*:data-feedback-island-content:animate-media-shake motion-reduce:data-min:*:data-feedback-island-content:animate-none motion-reduce:data-max:*:data-feedback-island-content:animate-none",
shownStatus: "data-open:duration-100",
icon: "hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]",
shownVolumeHigh: "group-data-[level=high]/input-indicator:block",
shownVolumeLow: "group-data-[level=low]/input-indicator:block",
shownVolumeOff: "group-data-[level=off]/input-indicator:block",
shownCaptionsOn: "group-data-[status=captions-on]/input-indicator:block",
shownCaptionsOff: "group-data-[status=captions-off]/input-indicator:block",
shownFullscreenEnter: "group-data-[status=fullscreen]/input-indicator:block",
shownFullscreenExit: "group-data-[status=exit-fullscreen]/input-indicator:block",
shownPipEnter: "group-data-[status=pip]/input-indicator:block",
shownPipExit: "group-data-[status=exit-pip]/input-indicator:block",
value: "ml-auto",
},
bubble: {
base: "group/input-indicator col-start-2 row-start-1 grid place-content-center text-center p-4 data-direction:gap-1 @2xl/media-root:data-direction:p-6 not-data-direction:[transition-property:opacity,scale] not-data-direction:duration-200 not-data-direction:ease-out motion-reduce:not-data-direction:transition-opacity motion-reduce:not-data-direction:duration-50 not-data-direction:data-starting-style:opacity-0 not-data-direction:data-ending-style:opacity-0 not-data-direction:data-starting-style:scale-[0.85] not-data-direction:data-ending-style:scale-[0.85] not-data-direction:data-ending-style:duration-100 not-data-direction:data-ending-style:ease-in data-[direction=backward]:col-start-1 data-[direction=backward]:justify-self-start data-[direction=forward]:col-start-3 data-[direction=forward]:justify-self-end",
icon: "hidden size-[calc(var(--media-icon-size)*2)]",
shownSeek: "group-data-direction/input-indicator:size-[calc(var(--media-icon-size)*1.5)] group-data-direction/input-indicator:block group-data-[direction=backward]/input-indicator:[scale:-1_1] motion-safe:transition-[translate,opacity] motion-safe:duration-200 motion-safe:ease-in-out motion-safe:group-data-starting-style/input-indicator:opacity-0 motion-safe:group-data-ending-style/input-indicator:opacity-0 motion-safe:group-data-[direction=forward]/input-indicator:group-data-starting-style/input-indicator:[translate:-60%_0] motion-safe:group-data-[direction=backward]/input-indicator:group-data-starting-style/input-indicator:[translate:60%_0]",
shownPause: "group-data-[status=pause]/input-indicator:block",
shownPlay: "group-data-[status=play]/input-indicator:block",
time: "tabular-nums",
},
};
const menu = {
root: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.5rem] [--menu-border-radius:1rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none overflow-auto! [--media-popover-side-offset:1.5rem] [--media-popover-boundary-offset:0.5rem] @2xl/media-root:[--media-popover-side-offset:0.5rem]",
settings: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.5rem] [--menu-border-radius:1rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none [--media-popup-transition:var(--media-popup-base-transition),height_var(--media-popup-transition-timing-function)_var(--menu-transition-duration),width_var(--media-popup-transition-timing-function)_var(--menu-transition-duration)] data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)] data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)] min-w-48 w-(--media-menu-width) h-(--media-menu-height) !overflow-hidden [--media-popover-side-offset:1.5rem] [--media-popover-boundary-offset:0.5rem] @2xl/media-root:[--media-popover-side-offset:0.5rem]",
group: "flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden",
item: "flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50",
separator: "my-1 border-b border-[oklch(1_0_0/0.1)]",
tier: "pl-0.5 pt-px text-[0.5625rem] font-semibold leading-none text-current/70",
indicator: "ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100",
icon: "shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]",
rootView: "absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] group/menu-root-view data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur",
submenuPanel: "absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full",
back: "flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full",
hint: "ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65",
hintLabel: "max-w-24 overflow-hidden text-ellipsis whitespace-nowrap",
chevron: "size-3.5",
settingsGroup: "group/settings",
settingsTrigger: "group hidden group-has-[[data-availability=available]]/settings:grid",
settingsIcon: "transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0",
};
const overlay = "absolute inset-0 flex flex-col items-start pointer-events-none rounded-[inherit] opacity-0 bg-linear-to-t from-black/70 via-black/50 via-[7.5rem] to-transparent backdrop-blur-none backdrop-saturate-100 transition-[opacity,backdrop-filter] duration-(--media-controls-transition-duration) ease-out peer-data-visible/controls:opacity-100 peer-data-visible/buffering:bg-black/35 peer-data-visible/buffering:opacity-100 peer-data-visible/buffering:backdrop-blur-sm peer-data-open/error:opacity-100 peer-data-open/error:duration-(--media-error-dialog-transition-duration) peer-data-open/error:delay-(--media-error-dialog-transition-delay) peer-data-open/error:backdrop-blur-lg peer-data-open/error:backdrop-saturate-120";
const popup = {
base: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full",
popover: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset)",
tooltip: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)",
tooltipShortcut: "min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded",
volume: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) py-3 px-0 bg-transparent rounded-xl [@media(prefers-reduced-transparency:reduce)]:bg-(--media-controls-background-color) contrast-more:bg-(--media-controls-background-color)",
};
const poster = () => "absolute inset-0 w-full h-full pointer-events-none transition-opacity duration-250 not-data-visible:opacity-0 rounded-[inherit] [object-fit:var(--media-object-fit,contain)] [object-position:var(--media-object-position,center)] [&[data-visible]:not([data-loaded])]:opacity-0";
const root = () => "**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,0.75rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 scrollbar-thin scrollbar-thumb-current/30 [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-auto [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-thumb-current/80 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] bg-black overflow-clip after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10 after:inset-0 after:ring-1 after:ring-inset after:ring-black/15 dark:after:ring-white/15 [&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&_video]:[object-fit:var(--media-object-fit,contain)] [&_video]:[object-position:var(--media-object-position,center)] [--media-video-border-radius:var(--media-border-radius,0.75rem)] [--media-controls-background-color:transparent] [--media-controls-transition-duration:100ms] [--media-controls-transition-timing-function:ease-out] [--media-error-dialog-transition-duration:150ms] [--media-error-dialog-transition-delay:100ms] [--media-error-dialog-transition-timing-function:ease-out] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-tooltip-background-color:oklch(0_0_0/0.5)] [--media-tooltip-border-color:oklch(1_0_0/0.1)] [--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)] [--media-tooltip-text-color:currentColor] [--media-tooltip-side-offset:0.5rem] [--media-tooltip-boundary-offset:0.5rem] [--media-popover-background-color:var(--media-tooltip-background-color)] [--media-popover-border-color:var(--media-tooltip-border-color)] [--media-popover-backdrop-filter:var(--media-tooltip-backdrop-filter)] [--media-popover-side-offset:1.5rem] [--media-popover-boundary-offset:var(--media-tooltip-boundary-offset)] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-popup-transition-duration:0ms] [@media(prefers-reduced-transparency:reduce)]:[--media-controls-background-color:oklch(0_0_0)] contrast-more:[--media-controls-background-color:oklch(0_0_0)] [@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(0_0_0)] contrast-more:[--media-tooltip-background-color:oklch(0_0_0)] @2xl/media-root:*:[--media-popover-side-offset:0.5rem] pointer-fine:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:300ms] pointer-coarse:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:150ms] motion-reduce:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:50ms] [--media-caption-track-y:-0.5rem] [--media-caption-track-delay:25ms] [--media-caption-track-duration:var(--media-controls-transition-duration)] has-[[data-controls][data-visible]]:[--media-caption-track-y:-5rem] @2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:-3rem] [&_video::-webkit-media-text-track-container]:transition-[translate] [&_video::-webkit-media-text-track-container]:duration-(--media-caption-track-duration) [&_video::-webkit-media-text-track-container]:ease-out [&_video::-webkit-media-text-track-container]:delay-(--media-caption-track-delay) [&_video::-webkit-media-text-track-container]:translate-y-(--media-caption-track-y) [&_video::-webkit-media-text-track-container]:scale-98 [&_video::-webkit-media-text-track-container]:z-1 [&_video::-webkit-media-text-track-container]:font-[inherit] before:absolute before:inset-0 before:pointer-events-none before:[background-image:var(--media-poster-placeholder,none)] before:bg-no-repeat before:[background-position:var(--media-object-position,center)] before:[background-size:var(--media-object-fit,contain)] before:opacity-0 before:[filter:blur(var(--media-poster-placeholder-blur,20px))] before:transition-opacity before:duration-250 has-[img[data-visible]:not([data-loaded])]:before:opacity-100 [&:fullscreen]:[--media-border-radius:0] [&:fullscreen_video]:object-contain";
const seek = {
label: "text-[10px] font-medium tracking-tighter tabular-nums",
labelForward: "absolute -right-px -bottom-0.75",
labelBackward: "absolute -left-px -bottom-0.75",
};
const slider = {
root: "group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]",
track: "relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-0.75 data-[orientation=vertical]:w-0.75 data-[orientation=vertical]:h-full ring-1 ring-black/5",
fill: {
base: "absolute rounded-[inherit] pointer-events-none",
fill: "bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)",
buffer: "bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer,0) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)",
},
thumb: {
base: "z-10 absolute size-3 -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,scale,outline-offset] duration-150 ease-out select-none outline-2 outline-transparent -outline-offset-2 focus-visible:outline-current focus-visible:outline-offset-2 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill,0))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))]",
interactive: "opacity-0 scale-70 origin-center group-hover/slider:opacity-100 group-hover/slider:scale-100 group-focus-within/slider:opacity-100 group-focus-within/slider:scale-100",
},
preview: "group/preview before:block before:bg-current/35 before:opacity-0 before:scale-50 before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 data-[orientation=horizontal]:before:min-w-px data-[orientation=horizontal]:before:h-5 data-[orientation=vertical]:before:w-5 data-[orientation=vertical]:before:min-h-px peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden",
value: "absolute bottom-6 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none",
};
const thumbnail = {
root: "group/thumbnail peer/thumbnail pointer-events-none [--media-slider-thumbnail-max-width:11rem] [--media-slider-thumbnail-max-height:8rem] [--media-slider-thumbnail-padding:-0.5rem] [--media-slider-thumbnail-inset:calc(100cqi-100%)] absolute [left:clamp(calc(var(--media-slider-thumbnail-max-width)/2+var(--media-slider-thumbnail-padding)),var(--media-slider-pointer),calc(100%-var(--media-slider-thumbnail-max-width)/2-var(--media-slider-thumbnail-padding)+var(--media-slider-thumbnail-inset)))] bottom-full -translate-x-1/2 @2xl/media-root:[left:var(--media-slider-pointer)] opacity-0 scale-80 blur-sm origin-bottom transition-[scale,opacity,filter] duration-150 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:opacity-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:scale-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:blur-none",
imageWrapper: "relative rounded-lg bg-black/90 after:absolute after:inset-0 after:rounded-[inherit] after:ring-1 after:ring-black/5 after:shadow-sm after:shadow-black/20",
image: "block rounded-[inherit] transition-opacity duration-150 ease-out data-loading:opacity-0 max-w-(--media-slider-thumbnail-max-width) max-h-(--media-slider-thumbnail-max-height)",
time: "mt-2 block text-center tabular-nums",
spinner: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity duration-150 ease-out group-not-has-[[role=img][data-loading]]/thumbnail:[--media-spinner-animation:none] group-has-[[role=img][data-loading]]/thumbnail:opacity-100",
};
const time = {
group: "flex items-center gap-1",
current: "hidden tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out supports-[corner-shape:squircle]:rounded-4 supports-[corner-shape:squircle]:[corner-shape:squircle] focus-visible:outline-current focus-visible:outline-offset-2 @2xl/media-root:inline",
separator: "hidden @2xl/media-root:inline @2xl/media-root:text-current/60",
duration: "tabular-nums @2xl/media-root:text-current/60",
controls: "@container flex flex-row-reverse items-center flex-1 gap-3 @2xl/media-root:flex-row grow-0 shrink-0 basis-full order-[-1] px-2.5 @2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]",
};
interface MinimalVideoSkinProps {
src: string;
style?: CSSProperties;
className?: string;
poster?: string | RenderProp<Poster.State> | undefined;
}
const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;
const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;
function MenuChevron({ flipped = false }: { flipped?: boolean }): ReactNode {
return <ChevronIcon className={`${icon} ${menu.icon} ${menu.chevron} ${flipped ? iconFlipped : undefined}`} />;
}
function SettingsMenu(): ReactNode {
const playbackRate = usePlaybackRateOptions();
const quality = useQualityOptions();
const audioTrack = useAudioTrackOptions();
const captions = useCaptionsOptions();
const hasPlaybackRate = playbackRate?.state.availability === 'available';
const hasQuality = quality?.state.availability === 'available';
const hasAudioTrack = audioTrack?.state.availability === 'available';
const hasCaptions = captions?.state.availability === 'available';
if (!hasPlaybackRate && !hasQuality && !hasAudioTrack && !hasCaptions) return null;
return (
<Menu.Root side="top" align="center">
<Menu.Trigger
aria-label="Settings"
className="media-button--settings"
render={<Button className={`${button.icon} ${menu.settingsTrigger}`} />}
>
<GearIcon className={`${icon} ${menu.settingsIcon}`} />
</Menu.Trigger>
<Menu.Content className={menu.settings}>
<Menu.View className={menu.rootView}>
<div className={menu.group}>
{hasQuality ? (
<Menu.Root>
<Menu.Trigger
type="quality"
className={`${menu.item} media-menu__item--submenu`}
render={(props) => (
<div {...props}>
<QualityIcon className={`${icon} ${menu.icon}`} />
<span>Quality</span>
<span className={menu.hint}>
<Menu.ItemValue className={menu.hintLabel} />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className={menu.submenuPanel}>
<Menu.Back className={menu.back}>
<MenuChevron flipped />
Quality
</Menu.Back>
<Menu.Separator className={menu.separator} />
<Menu.RadioGroup
className={menu.group}
value={quality.value}
onValueChange={quality.setValue}
aria-label="Quality"
>
{quality.options.map((option) => (
<Menu.RadioItem
key={option.value}
className={menu.item}
value={option.value}
disabled={option.disabled}
>
<span>
{option.label}
{option.tier ? <sup className={menu.tier}>{option.tier}</sup> : null}
</span>
{option.badge ? <span className={badge}>{option.badge}</span> : null}
<Menu.ItemIndicator
checked={option.value === quality.value}
forceMount
className={menu.indicator}
>
<CheckIcon className={`${icon} ${menu.icon}`} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasAudioTrack ? (
<Menu.Root>
<Menu.Trigger
type="audio-track"
className={`${menu.item} media-menu__item--submenu`}
render={(props) => (
<div {...props}>
<SpeechIcon className={icon} />
<span>Audio</span>
<span className={menu.hint}>
<Menu.ItemValue className={menu.hintLabel} />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className={menu.submenuPanel}>
<Menu.Back className={menu.back}>
<MenuChevron flipped />
Audio
</Menu.Back>
<Menu.Separator className={menu.separator} />
<Menu.RadioGroup
className={menu.group}
value={audioTrack.value}
onValueChange={audioTrack.setValue}
aria-label="Audio tracks"
>
{audioTrack.options.map((option) => (
<Menu.RadioItem
key={option.value}
className={menu.item}
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === audioTrack.value}
forceMount
className={menu.indicator}
>
<CheckIcon className={icon} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasPlaybackRate ? (
<Menu.Root>
<Menu.Trigger
type="playback-rate"
className={`${menu.item} media-menu__item--submenu`}
render={(props) => (
<div {...props}>
<SpeedIcon className={`${icon} ${menu.icon}`} />
<span>Speed</span>
<span className={menu.hint}>
<Menu.ItemValue className={menu.hintLabel} />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className={menu.submenuPanel}>
<Menu.Back className={menu.back}>
<MenuChevron flipped />
Speed
</Menu.Back>
<Menu.Separator className={menu.separator} />
<Menu.RadioGroup
className={menu.group}
value={playbackRate.value}
onValueChange={playbackRate.setValue}
aria-label="Playback rate"
>
{playbackRate.options.map((option) => (
<Menu.RadioItem
key={option.value}
className={menu.item}
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === playbackRate.value}
forceMount
className={menu.indicator}
>
<CheckIcon className={`${icon} ${menu.icon}`} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
{hasCaptions ? (
<Menu.Root>
<Menu.Trigger
type="captions"
className={`${menu.item} media-menu__item--submenu`}
render={(props) => (
<div {...props}>
<CaptionsOffIcon className={`${icon} ${menu.icon}`} />
<span>Captions</span>
<span className={menu.hint}>
<Menu.ItemValue className={menu.hintLabel} />
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className={menu.submenuPanel}>
<Menu.Back className={menu.back}>
<MenuChevron flipped />
Captions
</Menu.Back>
<Menu.Separator className={menu.separator} />
<Menu.RadioGroup
className={menu.group}
value={captions.value}
onValueChange={captions.setValue}
aria-label="Captions"
>
{captions.options.map((option) => (
<Menu.RadioItem
key={option.value}
className={menu.item}
value={option.value}
disabled={option.disabled}
>
<span>{option.label}</span>
<Menu.ItemIndicator
checked={option.value === captions.value}
forceMount
className={menu.indicator}
>
<CheckIcon className={`${icon} ${menu.icon}`} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
) : null}
</div>
</Menu.View>
</Menu.Content>
</Menu.Root>
);
}
export function MinimalVideoSkinTailwind({ children, className, poster: posterProp, placeholder, style, ...rest }: MinimalVideoSkinProps): ReactNode {
const containerStyle = placeholder
? ({ '--media-poster-placeholder': `url(${placeholder})`, ...style } as CSSProperties)
: style;
return (
<Container className={`${root(false)} ${className ?? ''}`} style={containerStyle} {...rest}>
{children}
{posterProp && (
<Poster
src={isString(posterProp) ? posterProp : undefined}
render={isRenderProp(posterProp) ? posterProp : undefined}
className={poster(false)}
/>
)}
<BufferingIndicator
render={(props) => (
<div {...props} className={bufferingIndicator}>
<SpinnerIcon className={icon} />
</div>
)}
/>
<ErrorDialog.Root>
<ErrorDialog.Popup className={error.root}>
<div className={error.dialog}>
<div className={error.content}>
<ErrorDialog.Title className={error.title}>Something went wrong.</ErrorDialog.Title>
<ErrorDialog.Description className={error.description} />
</div>
<div className={error.actions}>
<ErrorDialog.Close className={`${button.base} ${button.primary}`}>OK</ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root
data-controls="" // Used as a hook for Tailwind has-[] styles
className={controls}
>
<Tooltip.Provider>
<div className={buttonGroupStart}>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PlayButton className={iconState.play.button} render={<Button />}>
<RestartIcon className={`${icon} ${iconState.play.restart}`} />
<PlayIcon className={`${icon} ${iconState.play.play}`} />
<PauseIcon className={`${icon} ${iconState.play.pause}`} />
</PlayButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={`${icon} ${iconFlipped}`} />
<span className={`${seek.label} ${seek.labelBackward}`}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={icon} />
<span className={`${seek.label} ${seek.labelForward}`}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
</div>
<div className={time.controls}>
<Time.Group className={time.group}>
<Time.Value toggle type="current" className={time.current} />
<Time.Separator className={time.separator} />
<Time.Value type="duration" className={time.duration} />
</Time.Group>
<TimeSlider.Root render={<SliderRoot />}>
<TimeSlider.Track render={<SliderTrack />}>
<TimeSlider.Fill render={<SliderFill />} />
<TimeSlider.Buffer render={<SliderBuffer />} />
</TimeSlider.Track>
<TimeSlider.Thumb render={<SliderThumb />} />
<div className={thumbnail.root}>
<div className={thumbnail.imageWrapper}>
<Slider.Thumbnail className={thumbnail.image} />
</div>
<TimeSlider.Value type="pointer" className={thumbnail.time} />
<SpinnerIcon className={`${icon} ${thumbnail.spinner}`} />
</div>
<TimeSlider.Preview className={slider.preview}>
<TimeSlider.Value type="pointer" className={slider.value} />
</TimeSlider.Preview>
</TimeSlider.Root>
</div>
<div className={`${buttonGroupEnd} ${menu.settingsGroup}`}>
<VolumePopover />
<SettingsMenu />
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CastButton className={iconState.cast.button} render={<Button />}>
<CastEnterIcon className={`${icon} ${iconState.cast.enter}`} />
<CastExitIcon className={`${icon} ${iconState.cast.exit}`} />
</CastButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<AirPlayButton className={iconState.airplay.button} render={<Button />}>
<AirPlayEnterIcon className={`${icon} ${iconState.airplay.enter}`} />
<AirPlayExitIcon className={`${icon} ${iconState.airplay.exit}`} />
</AirPlayButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<PiPButton className={iconState.pip.button} render={<Button />}>
<PipEnterIcon className={`${icon} ${iconState.pip.off}`} />
<PipExitIcon className={`${icon} ${iconState.pip.on}`} />
</PiPButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<FullscreenButton className={iconState.fullscreen.button} render={<Button />}>
<FullscreenEnterIcon className={`${icon} ${iconState.fullscreen.enter}`} />
<FullscreenExitIcon className={`${icon} ${iconState.fullscreen.exit}`} />
</FullscreenButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
</div>
</Tooltip.Provider>
</Controls.Root>
<div className={overlay} />
{/* Hotkeys */}
<Hotkey keys="Space" action="togglePaused" />
<Hotkey keys="k" action="togglePaused" />
<Hotkey keys="m" action="toggleMuted" />
<Hotkey keys="f" action="toggleFullscreen" />
<Hotkey keys="c" action="toggleSubtitles" />
<Hotkey keys="i" action="togglePictureInPicture" />
<Hotkey keys="ArrowRight" action="seekStep" value={SEEK_TIME / 2} />
<Hotkey keys="ArrowLeft" action="seekStep" value={-(SEEK_TIME / 2)} />
<Hotkey keys="l" action="seekStep" value={SEEK_TIME} />
<Hotkey keys="j" action="seekStep" value={-SEEK_TIME} />
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
<Hotkey keys="0-9" action="seekToPercent" />
<Hotkey keys="Home" action="seekToPercent" value={0} />
<Hotkey keys="End" action="seekToPercent" value={100} />
<Hotkey keys=">" action="speedUp" />
<Hotkey keys="<" action="speedDown" />
{/* Gestures */}
<Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
<Gesture type="tap" action="toggleControls" pointer="touch" />
<Gesture type="doubletap" action="seekStep" value={-SEEK_TIME} region="left" />
<Gesture type="doubletap" action="toggleFullscreen" region="center" />
<Gesture type="doubletap" action="seekStep" value={SEEK_TIME} region="right" />
{/* Input Feedback */}
<StatusAnnouncer />
<div className={inputFeedback.root}>
<VolumeIndicator.Root
className={`${inputFeedback.island.base} ${inputFeedback.island.volume} ${inputFeedback.island.shownVolume}`}
>
<VolumeIndicator.Fill data-feedback-island-content="" className={inputFeedback.island.content}>
<VolumeHighIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeHigh}`} />
<VolumeLowIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeLow}`} />
<VolumeOffIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeOff}`} />
<div aria-hidden="true" className={inputFeedback.island.volumeProgress} />
<VolumeIndicator.Value className={inputFeedback.island.value} />
</VolumeIndicator.Fill>
</VolumeIndicator.Root>
<StatusIndicator.Root
actions={TOP_STATUS_ACTIONS}
className={`${inputFeedback.island.base} ${inputFeedback.island.shownStatus}`}
>
<div className={inputFeedback.island.content}>
<CaptionsOnIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownCaptionsOn}`} />
<CaptionsOffIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownCaptionsOff}`} />
<FullscreenEnterIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownFullscreenEnter}`} />
<FullscreenExitIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownFullscreenExit}`} />
<PipEnterIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownPipEnter}`} />
<PipExitIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownPipExit}`} />
<StatusIndicator.Value className={inputFeedback.island.value} />
</div>
</StatusIndicator.Root>
<SeekIndicator.Root className={inputFeedback.bubble.base}>
<ChevronIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownSeek}`} />
<SeekIndicator.Value className={inputFeedback.bubble.time} />
</SeekIndicator.Root>
<StatusIndicator.Root actions={CENTER_STATUS_ACTIONS} className={inputFeedback.bubble.base}>
<PlayIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownPlay}`} />
<PauseIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownPause}`} />
</StatusIndicator.Root>
</div>
</Container>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
// ================================================================
// Components
// ================================================================
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
return (
<button ref={ref} type="button" className={`${button.base} ${button.subtle} ${button.icon} ${className ?? ''}`} {...props} />
);
});
const SliderRoot = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderRoot({ className, ...props }, ref) {
return <div ref={ref} className={`${slider.root} ${className ?? ''}`} {...props} />;
});
const SliderTrack = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderTrack(
{ className, ...props },
ref
) {
return <div ref={ref} className={`${slider.track} ${className ?? ''}`} {...props} />;
});
const SliderFill = forwardRef<HTMLDivElement, ComponentProps<'div'> & { type?: 'fill' | 'buffer' }>(function SliderFill(
{ type = 'fill', className, ...props },
ref
) {
return (
<div
ref={ref}
className={`${slider.fill.base} ${type === 'fill' ? slider.fill.fill : slider.fill.buffer} ${className ?? ''}`}
{...props}
/>
);
});
const SliderBuffer = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderBuffer(props, ref) {
return <SliderFill type="buffer" ref={ref} {...props} />;
});
const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persistent?: boolean }>(function SliderThumb(
{ persistent, className, ...props },
ref
) {
return (
<div
ref={ref}
className={`${slider.thumb.base} ${persistent ? undefined : slider.thumb.interactive} ${className ?? ''}`}
{...props}
/>
);
});
function VolumePopover(): ReactNode {
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
const muteButton = (
<MuteButton className={iconState.mute.button} render={<Button />}>
<VolumeOffIcon className={`${icon} ${iconState.mute.volumeOff}`} />
<VolumeLowIcon className={`${icon} ${iconState.mute.volumeLow}`} />
<VolumeHighIcon className={`${icon} ${iconState.mute.volumeHigh}`} />
</MuteButton>
);
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="top">
<Popover.Trigger render={muteButton} />
<Popover.Popup className={`${popup.volume}`}>
<VolumeSlider.Root orientation="vertical" thumbAlignment="edge" render={<SliderRoot />}>
<VolumeSlider.Track render={<SliderTrack />}>
<VolumeSlider.Fill render={<SliderFill />} />
</VolumeSlider.Track>
<VolumeSlider.Thumb render={(props) => <SliderThumb persistent {...props} />} />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Root>
);
}
// ================================================================
// Utilities
// ================================================================
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isRenderProp(value: unknown): value is RenderProp<unknown> {
return typeof value === 'function' || isValidElement(value);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/video-minimal-ui.js"></script>
<link rel="stylesheet" href="./player.css">
<video-player>
<media-container class="media-minimal-skin media-minimal-skin--video">
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" playsinline></video>
<media-poster>
<img src="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp" />
</media-poster>
<media-buffering-indicator class="media-buffering-indicator">
<media-icon name="spinner" family="minimal" class="media-icon"></media-icon>
</media-buffering-indicator>
<media-error-dialog class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-alert-dialog-title class="media-error__title">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="media-error__description"></media-alert-dialog-description>
</div>
<div class="media-error__actions">
<media-alert-dialog-close class="media-button media-button--primary">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<media-controls class="media-controls">
<media-tooltip-group>
<div class="media-button-group">
<media-play-button commandfor="play-tooltip" class="media-button media-button--subtle media-button--icon media-button--play">
<media-icon name="restart" family="minimal" class="media-icon media-icon--restart"></media-icon>
<media-icon name="play" family="minimal" class="media-icon media-icon--play"></media-icon>
<media-icon name="pause" family="minimal" class="media-icon media-icon--pause"></media-icon>
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-backward-tooltip" seconds="-10" class="media-button media-button--subtle media-button--icon media-button--seek">
<span class="media-icon__container">
<media-icon name="seek" family="minimal" class="media-icon media-icon--flipped"></media-icon>
<span class="media-icon__label">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-backward-tooltip" side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-forward-tooltip" seconds="10" class="media-button media-button--subtle media-button--icon media-button--seek">
<span class="media-icon__container">
<media-icon name="seek" family="minimal" class="media-icon"></media-icon>
<span class="media-icon__label">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-forward-tooltip" side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
</div>
<div class="media-time-controls">
<media-time-group class="media-time-group">
<media-time toggle type="current" class="media-time media-time--current"></media-time>
<media-time-separator class="media-time-separator"></media-time-separator>
<media-time type="duration" class="media-time media-time--duration"></media-time>
</media-time-group>
<media-time-slider class="media-slider">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
<media-slider-buffer class="media-slider__buffer"></media-slider-buffer>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb"></media-slider-thumb>
<div class="media-thumbnail media-slider__thumbnail">
<div class="media-thumbnail__image-wrapper">
<media-slider-thumbnail class="media-thumbnail__image"></media-slider-thumbnail>
</div>
<media-slider-value type="pointer" class="media-time media-thumbnail__time"></media-slider-value>
<media-icon name="spinner" family="minimal" class="media-thumbnail__spinner media-icon"></media-icon>
</div>
<media-slider-preview class="media-slider__preview">
<media-slider-value type="pointer" class="media-slider__value media-time"></media-slider-value>
</media-slider-preview>
</media-time-slider>
</div>
<div class="media-button-group">
<media-mute-button commandfor="video-volume-popover" class="media-button media-button--subtle media-button--icon media-button--mute">
<media-icon name="volume-off" family="minimal" class="media-icon media-icon--volume-off"></media-icon>
<media-icon name="volume-low" family="minimal" class="media-icon media-icon--volume-low"></media-icon>
<media-icon name="volume-high" family="minimal" class="media-icon media-icon--volume-high"></media-icon>
</media-mute-button>
<media-popover id="video-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="media-popover media-popover--volume">
<media-volume-slider class="media-slider" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb media-slider__thumb--persistent"></media-slider-thumb>
</media-volume-slider>
</media-popover>
<button commandfor="settings-menu" aria-label="Settings" class="media-button media-button--subtle media-button--icon media-button--settings">
<media-icon name="gear" family="minimal" class="media-icon media-icon--settings"></media-icon>
</button>
<media-menu id="settings-menu" side="top" align="center" class="media-popover media-menu media-menu--settings">
<media-menu-view class="media-menu__panel">
<div class="media-menu__group">
<media-menu-item commandfor="settings-quality-menu" type="quality" data-setting="quality" class="media-menu__item media-menu__item--submenu">
<media-icon name="switches" family="minimal" class="media-icon"></media-icon>
<span>Quality</span>
<span class="media-menu__hint">
<media-menu-item-value class="media-menu__hint-label"></media-menu-item-value>
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-audio-menu" type="audio-track" data-setting="audio-track" class="media-menu__item media-menu__item--submenu">
<media-icon name="speech" family="minimal" class="media-icon"></media-icon>
<span>Audio</span>
<span class="media-menu__hint">
<media-menu-item-value class="media-menu__hint-label"></media-menu-item-value>
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-speed-menu" type="playback-rate" data-setting="playback-rate" class="media-menu__item media-menu__item--submenu">
<media-icon name="speed" family="minimal" class="media-icon"></media-icon>
<span>Speed</span>
<span class="media-menu__hint">
<media-menu-item-value class="media-menu__hint-label"></media-menu-item-value>
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-captions-menu" type="captions" data-setting="captions" class="media-menu__item media-menu__item--submenu">
<media-icon name="captions-off" family="minimal" class="media-icon"></media-icon>
<span>Captions</span>
<span class="media-menu__hint">
<media-menu-item-value class="media-menu__hint-label"></media-menu-item-value>
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
</div>
</media-menu-view>
<media-menu id="settings-quality-menu" class="media-menu__panel">
<media-menu-back class="media-menu__back">
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
Quality
</media-menu-back>
<div class="media-menu__separator"></div>
<media-quality-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span>
<span data-part="label"></span>
<sup data-part="tier" class="media-menu__tier"></sup>
</span>
<span data-part="badge" class="media-badge"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" family="minimal" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-quality-radio-group>
</media-menu>
<media-menu id="settings-audio-menu" class="media-menu__panel">
<media-menu-back class="media-menu__back">
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
Audio
</media-menu-back>
<div class="media-menu__separator"></div>
<media-audio-track-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" family="minimal" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-audio-track-radio-group>
</media-menu>
<media-menu id="settings-speed-menu" class="media-menu__panel">
<media-menu-back class="media-menu__back">
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
Speed
</media-menu-back>
<div class="media-menu__separator"></div>
<media-playback-rate-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" family="minimal" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
</media-menu>
<media-menu id="settings-captions-menu" class="media-menu__panel">
<media-menu-back class="media-menu__back">
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
Captions
</media-menu-back>
<div class="media-menu__separator"></div>
<media-captions-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" family="minimal" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-captions-radio-group>
</media-menu>
</media-menu>
<media-cast-button commandfor="cast-tooltip" class="media-button media-button--subtle media-button--icon media-button--cast">
<media-icon name="cast-enter" family="minimal" class="media-icon media-icon--cast-enter"></media-icon>
<media-icon name="cast-exit" family="minimal" class="media-icon media-icon--cast-exit"></media-icon>
</media-cast-button>
<media-tooltip id="cast-tooltip" side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-airplay-button commandfor="airplay-tooltip" class="media-button media-button--subtle media-button--icon media-button--airplay">
<media-icon name="airplay-enter" family="minimal" class="media-icon media-icon--airplay-enter"></media-icon>
<media-icon name="airplay-exit" family="minimal" class="media-icon media-icon--airplay-exit"></media-icon>
</media-airplay-button>
<media-tooltip id="airplay-tooltip" side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-pip-button commandfor="pip-tooltip" class="media-button media-button--subtle media-button--icon media-button--pip">
<media-icon name="pip-enter" family="minimal" class="media-icon media-icon--pip-enter"></media-icon>
<media-icon name="pip-exit" family="minimal" class="media-icon media-icon--pip-exit"></media-icon>
</media-pip-button>
<media-tooltip id="pip-tooltip" side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-fullscreen-button commandfor="fullscreen-tooltip" class="media-button media-button--subtle media-button--icon media-button--fullscreen">
<media-icon name="fullscreen-enter" family="minimal" class="media-icon media-icon--fullscreen-enter"></media-icon>
<media-icon name="fullscreen-exit" family="minimal" class="media-icon media-icon--fullscreen-exit"></media-icon>
</media-fullscreen-button>
<media-tooltip id="fullscreen-tooltip" side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
</div>
</media-tooltip-group>
</media-controls>
<div class="media-overlay"></div>
<!-- Hotkeys -->
<media-hotkey keys="Space" action="togglePaused"></media-hotkey>
<media-hotkey keys="k" action="togglePaused"></media-hotkey>
<media-hotkey keys="m" action="toggleMuted"></media-hotkey>
<media-hotkey keys="f" action="toggleFullscreen"></media-hotkey>
<media-hotkey keys="c" action="toggleSubtitles"></media-hotkey>
<media-hotkey keys="i" action="togglePictureInPicture"></media-hotkey>
<media-hotkey keys="ArrowRight" action="seekStep" value="5"></media-hotkey>
<media-hotkey keys="ArrowLeft" action="seekStep" value="-5"></media-hotkey>
<media-hotkey keys="l" action="seekStep" value="10"></media-hotkey>
<media-hotkey keys="j" action="seekStep" value="-10"></media-hotkey>
<media-hotkey keys="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
<media-hotkey keys="0-9" action="seekToPercent"></media-hotkey>
<media-hotkey keys="Home" action="seekToPercent" value="0"></media-hotkey>
<media-hotkey keys="End" action="seekToPercent" value="100"></media-hotkey>
<media-hotkey keys=">" action="speedUp"></media-hotkey>
<media-hotkey keys="<" action="speedDown"></media-hotkey>
<!-- Gestures -->
<media-gesture type="tap" action="togglePaused" pointer="mouse" region="center"></media-gesture>
<media-gesture type="tap" action="toggleControls" pointer="touch"></media-gesture>
<media-gesture type="doubletap" action="seekStep" value="-10" region="left"></media-gesture>
<media-gesture type="doubletap" action="toggleFullscreen" region="center"></media-gesture>
<media-gesture type="doubletap" action="seekStep" value="10" region="right"></media-gesture>
<!-- Input Feedback -->
<media-status-announcer></media-status-announcer>
<div class="media-input-feedback">
<media-volume-indicator hidden class="media-input-feedback-island media-input-feedback-island--volume">
<media-volume-indicator-fill class="media-input-feedback-island__content">
<media-icon name="volume-high" family="minimal" class="media-icon media-icon--volume-high"></media-icon>
<media-icon name="volume-low" family="minimal" class="media-icon media-icon--volume-low"></media-icon>
<media-icon name="volume-off" family="minimal" class="media-icon media-icon--volume-off"></media-icon>
<div class="media-input-feedback-island__progress" aria-hidden="true"></div>
<media-volume-indicator-value class="media-input-feedback-island__value"></media-volume-indicator-value>
</media-volume-indicator-fill>
</media-volume-indicator>
<media-status-indicator hidden actions="toggleSubtitles toggleFullscreen togglePictureInPicture" class="media-input-feedback-island media-input-feedback-island--status">
<div class="media-input-feedback-island__content">
<media-icon name="captions-on" family="minimal" class="media-icon media-icon--captions-on"></media-icon>
<media-icon name="captions-off" family="minimal" class="media-icon media-icon--captions-off"></media-icon>
<media-icon name="fullscreen-enter" family="minimal" class="media-icon media-icon--fullscreen-enter"></media-icon>
<media-icon name="fullscreen-exit" family="minimal" class="media-icon media-icon--fullscreen-exit"></media-icon>
<media-icon name="pip-enter" family="minimal" class="media-icon media-icon--pip-enter"></media-icon>
<media-icon name="pip-exit" family="minimal" class="media-icon media-icon--pip-exit"></media-icon>
<media-status-indicator-value class="media-input-feedback-island__value"></media-status-indicator-value>
</div>
</media-status-indicator>
<media-seek-indicator hidden class="media-input-feedback-bubble">
<media-icon name="chevron" family="minimal" class="media-icon media-icon--seek"></media-icon>
<media-seek-indicator-value class="media-time"></media-seek-indicator-value>
</media-seek-indicator>
<media-status-indicator hidden actions="togglePaused" class="media-input-feedback-bubble">
<media-icon name="play" family="minimal" class="media-icon media-icon--play"></media-icon>
<media-icon name="pause" family="minimal" class="media-icon media-icon--pause"></media-icon>
</media-status-indicator>
</div>
</media-container>
</video-player>
/* -------------------------------------------------------------------------- */
/* Global styles for the host document, outside of the Shadow DOM */
/* -------------------------------------------------------------------------- */
video-player,
live-video-player {
display: contents;
}
/*
Required to override any default video and image styles (such as
Tailwind's CSS reset) and ensure they fill the container as expected.
*/
video-player video,
video-player [slot="poster"],
live-video-player video,
live-video-player [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
video-player video::-webkit-media-text-track-container,
live-video-player video::-webkit-media-text-track-container {
z-index: 1;
font-family: inherit;
scale: 0.98;
translate: 0 var(--media-caption-track-y, 0);
transition: translate var(--media-caption-track-duration, 0) ease-out;
transition-delay: var(--media-caption-track-delay, 0);
}
/* -------------------------------------------------------------------------- */
/* Shared styles for all HTML skins */
/* -------------------------------------------------------------------------- */
media-tooltip-group {
display: contents;
}
:host {
/* `display:grid` fixes a weird issue with Safari when setting aspect-ratio */
display: grid;
width: 100%;
}
media-container {
min-width: 0;
min-height: 0;
}
/* Hide volume popover when volume control is unsupported (e.g., iOS Safari). */
.media-popover--volume:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
/* ==========================================================================
Reset
========================================================================== */
.media-minimal-skin *,
.media-minimal-skin *::before,
.media-minimal-skin *::after {
box-sizing: border-box;
}
.media-minimal-skin img,
.media-minimal-skin video,
.media-minimal-skin svg {
display: block;
max-width: 100%;
}
.media-minimal-skin button {
font: inherit;
}
.media-minimal-skin [hidden][hidden] {
/* Keep authored templates hidden even when component classes set display. */
display: none;
}
@media (prefers-reduced-motion: no-preference) {
.media-minimal-skin {
interpolate-size: allow-keywords;
}
}
/* ==========================================================================
Root Container
========================================================================== */
.media-minimal-skin {
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
--media-icon-size: 18px;
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
position: relative;
display: block;
width: 100%;
height: 100%;
container: media-root / inline-size;
font-family:
Inter Variable,
Inter,
ui-sans-serif,
system-ui,
sans-serif;
font-size: 0.8125rem; /* 13px at 100% font size */
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
scrollbar-width: thin;
border-radius: var(--media-border-radius, 0.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-color-scrollbar-thumb);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
/* ==========================================================================
Media Element
========================================================================== */
.media-minimal-skin ::slotted(video),
.media-minimal-skin video {
display: block;
width: 100%;
height: 100%;
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
}
.media-minimal-skin ::slotted(video) {
border-radius: var(--media-video-border-radius);
}
.media-minimal-skin video {
border-radius: inherit;
}
.media-minimal-skin:fullscreen ::slotted(video),
.media-minimal-skin:fullscreen video {
object-fit: contain;
}
/* ==========================================================================
Overlay / Scrim
========================================================================== */
.media-minimal-skin .media-overlay {
position: absolute;
inset: 0;
pointer-events: none;
background-image: linear-gradient(to top, oklch(0 0 0 / 0.7), oklch(0 0 0 / 0.5) 7.5rem, oklch(0 0 0 / 0));
border-radius: inherit;
opacity: 0;
backdrop-filter: blur(0) saturate(1);
transition-timing-function: ease-out;
transition-duration: var(--media-controls-transition-duration);
transition-property: opacity, backdrop-filter;
}
.media-minimal-skin .media-error ~ .media-overlay {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-minimal-skin .media-controls[data-visible] ~ .media-overlay,
.media-minimal-skin .media-error[data-open] ~ .media-overlay {
opacity: 1;
}
.media-minimal-skin .media-buffering-indicator[data-visible] ~ .media-overlay {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-minimal-skin .media-error[data-open] ~ .media-overlay {
backdrop-filter: blur(16px) saturate(1.2);
}
/* ==========================================================================
Buffering Indicator
========================================================================== */
.media-minimal-skin .media-buffering-indicator {
position: absolute;
inset: 0;
z-index: 10;
display: none;
place-content: center;
color: oklch(1 0 0);
pointer-events: none;
&:not([data-visible]) {
--media-spinner-animation: none;
}
&[data-visible] {
display: grid;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-minimal-skin .media-error:not([data-open]) {
display: none;
}
.media-minimal-skin .media-error__title {
font-weight: 600;
line-height: 1.25;
}
.media-minimal-skin .media-error__description {
overflow-wrap: anywhere;
opacity: 0.7;
}
.media-minimal-skin .media-error__actions {
display: flex;
gap: 0.5rem;
& > * {
flex: 1;
}
}
.media-minimal-skin .media-error[data-open] ~ .media-controls * {
visibility: hidden;
}
/* ==========================================================================
Controls
========================================================================== */
.media-minimal-skin .media-controls {
display: flex;
align-items: center;
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
}
/* ==========================================================================
Time Controls & Display
========================================================================== */
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: 0.75rem;
align-items: center;
container: media-time-controls / inline-size;
}
.media-minimal-skin .media-time-group {
display: flex;
gap: 0.25rem;
align-items: center;
}
.media-minimal-skin .media-time {
font-variant-numeric: tabular-nums;
}
.media-minimal-skin .media-time[role="button"] {
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: 0.25rem;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: 1rem;
corner-shape: squircle;
}
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
.media-minimal-skin .media-time--current,
.media-minimal-skin .media-time-separator {
display: none;
}
@container media-root (width > 42rem) {
.media-minimal-skin .media-time-controls {
flex-direction: row;
}
.media-minimal-skin .media-time--duration,
.media-minimal-skin .media-time-separator {
color: oklch(from currentColor l c h / 0.6);
}
.media-minimal-skin .media-time--current,
.media-minimal-skin .media-time-separator {
display: inline;
}
}
/* ==========================================================================
Buttons
========================================================================== */
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
min-height: 0;
padding: 0.5rem 1rem;
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: 0.5rem;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:active {
scale: 0.98;
}
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: 1rem;
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: oklch(0 0 0);
text-shadow: none;
background: oklch(1 0 0);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
background: oklch(from currentColor l c h / 0.1);
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
width: 2.375rem;
aspect-ratio: 1;
padding: 0;
&:active {
scale: 0.9;
}
& .media-icon__container {
display: grid;
}
& .media-icon {
grid-area: 1 / 1;
transition-behavior: allow-discrete;
transition-property: display, opacity;
transition-duration: 150ms;
transition-timing-function: ease-out;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
& .media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 10px; /* Hard coded due to size limitations. */
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
}
&:has(.media-icon--flipped) .media-icon__label {
right: unset;
left: -1px;
}
}
/* Playback rate button */
.media-minimal-skin .media-button--playback-rate {
padding: 0;
font-variant-numeric: tabular-nums;
&::after {
width: 4ch;
content: attr(data-rate) "\00D7";
}
&[data-inline-rate-label]::after {
content: none;
}
}
/* Settings button */
.media-minimal-skin .media-button--settings {
display: none;
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
&[aria-expanded="true"] .media-icon--settings {
transform: rotate(90deg);
}
}
.media-minimal-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
display: grid;
}
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
text content. */
.media-minimal-skin .media-button--live {
display: inline-flex;
gap: 0.4rem;
align-items: center;
width: auto;
aspect-ratio: auto;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
transition: background-color 150ms ease-out;
}
&[data-live-edge]::before {
background-color: oklch(0.65 0.22 27);
}
}
/* ==========================================================================
Button Groups
========================================================================== */
.media-minimal-skin .media-button-group {
display: flex;
gap: 0.075rem;
align-items: center;
@container media-root (width > 42rem) {
gap: 0.125rem;
}
}
/* ==========================================================================
Badge
========================================================================== */
.media-minimal-skin .media-badge {
padding: 0.25rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
border-radius: 0.25rem;
}
/* ==========================================================================
Icons
========================================================================== */
.media-minimal-skin .media-icon__container {
position: relative;
}
.media-minimal-skin .media-icon {
flex-shrink: 0;
width: var(--media-icon-size);
height: var(--media-icon-size);
}
.media-minimal-skin .media-icon--flipped {
scale: -1 1;
}
/* ==========================================================================
Poster Image
========================================================================== */
.media-minimal-skin media-poster,
.media-minimal-skin > img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
transition: opacity 0.25s;
}
.media-minimal-skin media-poster:not([data-visible]),
.media-minimal-skin > img:not([data-visible]),
.media-minimal-skin > img[data-visible]:not([data-loaded]) {
opacity: 0;
}
.media-minimal-skin media-poster ::slotted(img),
.media-minimal-skin media-poster img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: var(--media-video-border-radius);
}
.media-minimal-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
/* Blurred placeholder: HTML path targets media-poster::before, React path targets the skin container::before */
.media-minimal-skin media-poster::before,
.media-minimal-skin::before {
position: absolute;
inset: 0;
pointer-events: none;
content: "";
background-image: var(--media-poster-placeholder, none);
background-repeat: no-repeat;
background-position: var(--media-object-position, center);
background-size: var(--media-object-fit, contain);
filter: blur(var(--media-poster-placeholder-blur, 20px));
}
/* React path: hidden by default, transitions in while poster loads */
.media-minimal-skin::before {
opacity: 0;
transition: opacity 0.25s;
}
/* Show placeholder while the poster is visible but not yet loaded */
.media-minimal-skin:has(img[data-visible]:not([data-loaded]))::before {
opacity: 1;
}
.media-minimal-skin:fullscreen media-poster ::slotted(img),
.media-minimal-skin:fullscreen media-poster img,
.media-minimal-skin:fullscreen > img {
object-fit: contain;
}
/* ==========================================================================
Media thumbnail
========================================================================== */
.media-minimal-skin .media-thumbnail {
pointer-events: none;
& .media-thumbnail__image-wrapper {
position: relative;
background-color: oklch(0 0 0 / 0.9);
border-radius: 0.5rem;
}
& .media-thumbnail__image {
display: block;
border-radius: inherit;
}
& .media-thumbnail__time {
display: block;
margin-top: 0.5rem;
text-align: center;
}
& .media-overlay {
opacity: 1;
}
& .media-thumbnail__spinner {
position: absolute;
top: 50%;
left: 50%;
opacity: 0;
translate: -50% -50%;
}
& .media-thumbnail__image,
& .media-thumbnail__spinner {
transition: opacity 150ms ease-out;
}
&:not(:has(.media-thumbnail__image[data-loading])) {
& .media-thumbnail__spinner {
--media-spinner-animation: none;
}
}
&:has(.media-thumbnail__image[data-loading]) {
& .media-thumbnail__image {
opacity: 0;
}
& .media-thumbnail__spinner {
opacity: 1;
}
}
}
/* ==========================================================================
Slider
========================================================================== */
.media-minimal-skin .media-slider {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: calc(infinity * 1px);
&[data-orientation="horizontal"] {
width: 100%;
min-width: 5rem;
height: 2rem;
}
&[data-orientation="vertical"] {
width: 2rem;
height: 4.5rem;
}
}
/* Track */
.media-minimal-skin .media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: inherit;
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: 0.1875rem;
}
&[data-orientation="vertical"] {
width: 0.1875rem;
height: 100%;
}
}
/* Thumb */
.media-minimal-skin .media-slider__thumb {
position: absolute;
z-index: 10;
width: 0.75rem;
height: 0.75rem;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
background-color: currentColor;
border-radius: calc(infinity * 1px);
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
0 1px 3px 0 oklch(0 0 0 / 0.15),
0 1px 2px -1px oklch(0 0 0 / 0.15);
opacity: 0;
transform-origin: center;
scale: 0.7;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale, outline-offset;
&[data-orientation="horizontal"] {
top: 50%;
left: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-fill));
left: 50%;
}
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
.media-minimal-skin .media-slider:hover .media-slider__thumb,
.media-minimal-skin .media-slider:focus-within .media-slider__thumb,
.media-minimal-skin .media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
/* Preview */
.media-minimal-skin .media-slider__preview {
& .media-slider__value,
&::before {
opacity: 0;
scale: 0.5;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
& .media-slider__value {
position: absolute;
bottom: 1.5rem;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
filter: blur(8px);
translate: -50% 0.5rem;
transition-property: filter, opacity, scale, translate;
}
&::before {
display: block;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
}
&[data-pointing] .media-slider__value,
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
&[data-pointing] .media-slider__value {
filter: blur(0);
translate: -50% 0;
}
&[data-orientation="horizontal"]::before {
min-width: 1px;
height: 1.25rem;
}
&[data-orientation="vertical"]::before {
width: 1.25rem;
min-height: 1px;
}
}
/* Shared track fills */
.media-minimal-skin .media-slider__buffer,
.media-minimal-skin .media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
}
.media-minimal-skin .media-slider__buffer[data-orientation="horizontal"],
.media-minimal-skin .media-slider__fill[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
}
.media-minimal-skin .media-slider__buffer[data-orientation="vertical"],
.media-minimal-skin .media-slider__fill[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
}
/* Buffer */
.media-minimal-skin .media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
transition-timing-function: ease-out;
transition-duration: 0.25s;
&[data-orientation="horizontal"] {
width: var(--media-slider-buffer);
transition-property: width;
}
&[data-orientation="vertical"] {
height: var(--media-slider-buffer);
transition-property: height;
}
}
/* Fill */
.media-minimal-skin .media-slider__fill {
background-color: currentColor;
&[data-orientation="horizontal"] {
width: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
height: var(--media-slider-fill);
}
}
/* Dragging — thumb and fill follow the pointer position */
.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
width: var(--media-slider-pointer);
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
height: var(--media-slider-pointer);
}
/* ==========================================================================
Popups & Animations
========================================================================== */
.media-minimal-skin {
--media-popup-transition:
opacity var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
filter var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
transform var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
scale var(--media-popup-transition-timing-function) var(--media-popup-transition-duration);
}
.media-minimal-skin .media-popover,
.media-minimal-skin .media-tooltip {
--popup-translate-distance: 0.5rem;
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
filter: blur(4px);
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--popup-translate-x-distance, 0), var(--popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--popup-translate-y-distance: var(--popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--popup-translate-y-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--popup-translate-x-distance: var(--popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--popup-translate-x-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: left;
}
/* Safe area between trigger and popup */
&::before {
position: absolute;
pointer-events: inherit;
content: "";
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
inset-inline: 0;
width: 100%;
}
&[data-side="top"]::before {
top: 100%;
}
&[data-side="bottom"]::before {
bottom: 100%;
}
&[data-side="left"]::before,
&[data-side="right"]::before {
inset-block: 0;
height: 100%;
}
&[data-side="left"]::before {
left: 100%;
}
&[data-side="right"]::before {
right: 100%;
}
}
.media-minimal-skin .media-popover {
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-popover-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-popover-side-offset);
}
}
.media-minimal-skin .media-tooltip {
padding: 0.25rem 0.5rem;
font-size: 0.75rem; /* 12px at 100% font size */
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: 0.5rem;
box-shadow:
0 0 0 1px var(--media-tooltip-border-color),
0 4px 6px -1px oklch(0 0 0 / 0.2),
0 2px 4px -2px oklch(0 0 0 / 0.2);
backdrop-filter: var(--media-tooltip-backdrop-filter);
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
column-gap: 0.25rem;
align-items: center;
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-tooltip-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-tooltip-side-offset);
}
& .media-tooltip__kbd {
min-width: 1.5em;
padding: 0.1em;
margin-right: -0.25rem;
font-family: inherit;
font-size: 90%;
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: 0.25rem;
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
/* ==========================================================================
Menus
Note: Menus use `.media-popover` styles for positioning and transitions.
========================================================================== */
.media-minimal-skin .media-menu {
--menu-transition-duration: 250ms;
--menu-max-height: 14rem;
--menu-padding: 0.5rem;
--menu-border-radius: 1rem;
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--menu-border-radius);
box-shadow:
0 0 0 1px var(--media-popover-border-color),
0 4px 6px -1px oklch(0 0 0 / 0.1),
0 2px 4px -2px oklch(0 0 0 / 0.1);
backdrop-filter: var(--media-popover-backdrop-filter);
@media (prefers-reduced-motion: reduce) {
--menu-transition-duration: 0ms;
}
& .media-menu__panel {
position: absolute;
inset: 0;
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-in-out;
transition-duration: var(--menu-transition-duration);
transition-property: translate, filter;
will-change: translate, filter;
&[data-starting-style],
&[data-ending-style] {
overflow: hidden;
}
/* Root settings view — slides out when a submenu is active */
&[data-menu-root-view][data-menu-view-state="inactive"] {
filter: blur(8px);
translate: -100% 0;
/* Hide the indicator while transitioning */
& .media-menu__group::before {
display: none;
}
}
/* Submenu panels — slide in/out alongside the root view */
&[data-submenu] {
z-index: 10;
&:not([data-open], [data-ending-style]) {
translate: -100% 0;
transition-property: none;
}
&[data-starting-style],
&[data-ending-style] {
pointer-events: none;
filter: blur(8px);
}
&[data-starting-style][data-direction="forward"],
&[data-ending-style][data-direction="back"] {
translate: 100% 0;
}
&[data-ending-style][data-direction="forward"],
&[data-starting-style][data-direction="back"] {
translate: -100% 0;
}
}
}
& .media-menu__separator {
margin: 0.25rem 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
& .media-menu__group {
display: flex;
flex-direction: column;
gap: 0.125rem;
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --media-menu-item-highlight-anchor;
inset: anchor(inside);
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.1);
border-radius: var(--menu-item-border-radius);
transition: inset ease-in-out 100ms;
}
}
}
& .media-menu__item,
& .media-menu__back {
display: flex;
gap: 0.375rem;
align-items: center;
padding: 0.375rem 0.5rem;
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-out;
transition-duration: 100ms;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
@supports (top: anchor(top)) {
anchor-name: --media-menu-item-highlight-anchor;
background-color: transparent;
}
}
& .media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__indicator {
flex-shrink: 0;
margin-right: -0.25rem;
margin-left: auto;
opacity: 0;
}
& .media-menu__item {
justify-content: space-between;
font-variant-numeric: tabular-nums;
color: inherit;
&[aria-disabled="true"] {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
&[aria-checked="true"] .media-menu__indicator {
opacity: 1;
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
& .media-menu__tier {
padding-top: 1px;
padding-left: 0.125rem;
font-size: 0.5625rem;
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
& .media-menu__back {
width: 100%;
margin-bottom: 0.125rem;
}
& .media-menu__hint {
display: inline-flex;
gap: 0.25rem;
align-items: center;
min-width: 0;
padding-left: 0.5rem;
margin-left: auto;
font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
max-width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
width: 0.875rem;
height: 0.875rem;
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: 12rem;
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
transition:
var(--media-popup-transition),
height var(--media-popup-transition-timing-function) var(--menu-transition-duration),
width var(--media-popup-transition-timing-function) var(--menu-transition-duration);
/* Don't transition width and height on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
/* ==========================================================================
Native Caption Track
========================================================================== */
.media-minimal-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: -0.5rem;
&:has(.media-controls[data-visible]) {
--media-caption-track-y: -5rem;
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
--media-caption-track-y: -3rem;
}
}
}
.media-minimal-skin video::-webkit-media-text-track-container {
z-index: 1;
font-family: inherit;
scale: 0.98;
translate: 0 var(--media-caption-track-y);
transition: translate var(--media-caption-track-duration) ease-out;
transition-delay: var(--media-caption-track-delay);
}
/* ==========================================================================
Input Feedback
========================================================================== */
.media-minimal-skin .media-input-feedback {
position: absolute;
inset-inline: 0;
top: 0;
bottom: 3.5rem; /* Shift up a little in smaller containers */
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
overflow: hidden;
color: var(--media-color-primary, oklch(1 0 0));
pointer-events: none;
border-radius: inherit;
@container media-root (width > 24rem) {
bottom: 0;
}
}
/* --- Feedback islands ------------------------------------------------------- */
.media-minimal-skin .media-input-feedback-island {
position: absolute;
inset-inline: 0;
top: 0;
display: flex;
justify-content: center;
padding-top: 0.75rem;
padding-bottom: 8rem;
color: inherit;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
pointer-events: none;
background-image: linear-gradient(to bottom, oklch(0 0 0 / 0.35), oklch(0 0 0 / 0.2) 3rem, oklch(0 0 0 / 0));
transform-origin: top center;
transition-timing-function: ease-out;
transition-duration: 100ms;
.media-input-feedback-island__content {
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: space-between;
padding: 0.25rem 0.625rem;
}
.media-icon {
display: none;
flex-shrink: 0;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
.media-input-feedback-island__value {
margin-left: auto;
}
@media (pointer: fine) {
transition-property: translate, filter, opacity;
will-change: translate, filter, opacity;
}
@media (pointer: coarse) {
transition-property: translate, opacity;
will-change: translate, opacity;
}
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
transition-property: translate, filter, opacity;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
.media-input-feedback-island__content {
background: var(--media-controls-background-color);
border-radius: 0.5rem;
}
}
/* Default hidden state */
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
transition-timing-function: ease-in;
transition-duration: 400ms;
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: no-preference) {
&[data-ending-style] {
translate: 0 -100%;
}
}
}
}
.media-minimal-skin .media-input-feedback-island--volume {
.media-input-feedback-island__content {
width: min(80%, 14rem);
}
.media-input-feedback-island__progress {
--media-progress-fill: var(--media-volume-fill);
width: 100%;
height: 0.1875rem;
background-image: linear-gradient(
to right,
currentColor 0%,
currentColor var(--media-progress-fill),
oklch(from currentColor l c h / 0.2) var(--media-progress-fill),
oklch(from currentColor l c h / 0.2) 100%
);
border-radius: calc(Infinity * 1px);
box-shadow: 0 1px 0 var(--media-current-shadow-color-subtle);
}
&[data-level="high"] .media-icon--volume-high,
&[data-level="low"] .media-icon--volume-low,
&[data-level="off"] .media-icon--volume-off {
display: block;
}
@media (prefers-reduced-motion: no-preference) {
&[data-min] .media-input-feedback-island__content,
&[data-max] .media-input-feedback-island__content {
animation: media-shake 300ms ease-in-out;
}
}
}
.media-minimal-skin .media-input-feedback-island--status {
&[data-status="captions-on"] .media-icon--captions-on,
&[data-status="captions-off"] .media-icon--captions-off,
&[data-status="fullscreen"] .media-icon--fullscreen-enter,
&[data-status="exit-fullscreen"] .media-icon--fullscreen-exit,
&[data-status="pip"] .media-icon--pip-enter,
&[data-status="exit-pip"] .media-icon--pip-exit {
display: block;
}
}
/* --- Bubble ---------------------------------------------------------------- */
.media-minimal-skin .media-input-feedback-bubble {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: 1rem;
text-align: center;
/* Central bubble (play, pause) */
&:not([data-direction]) {
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
scale: 0.85;
}
&[data-ending-style] {
transition-timing-function: ease-in;
transition-duration: 100ms;
}
@media (prefers-reduced-motion: reduce) {
transition-duration: 50ms;
transition-property: opacity;
}
}
/* Directional bubbles (seek) */
&[data-direction] {
gap: 0.25rem;
@container media-root (width > 24rem) {
padding: 1.5rem;
}
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
/* Icons */
.media-icon {
display: none;
width: calc(var(--media-icon-size) * 2);
height: calc(var(--media-icon-size) * 2);
}
.media-icon--seek {
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[data-direction] .media-icon--seek,
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
display: block;
}
&[data-direction="backward"] .media-icon--seek {
scale: -1 1;
}
@media (prefers-reduced-motion: no-preference) {
.media-icon--seek {
transition-timing-function: ease-in-out;
transition-duration: 200ms;
transition-property: translate, opacity;
}
&[data-starting-style] .media-icon--seek,
&[data-ending-style] .media-icon--seek {
opacity: 0;
}
&[data-direction="forward"][data-starting-style] .media-icon--seek {
translate: -60% 0;
}
&[data-direction="backward"][data-starting-style] .media-icon--seek {
translate: 60% 0;
}
}
}
/* ==========================================================================
Icon State Visibility for Video Skins
Data-attribute-driven visibility rules for multi-state icon buttons.
Uses :is() with both element selectors (for HTML custom element wrappers)
and class selectors (for React rendered SVG elements).
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon--restart,
.media-button--play .media-icon--play,
.media-button--play .media-icon--pause,
.media-button--mute .media-icon--volume-off,
.media-button--mute .media-icon--volume-low,
.media-button--mute .media-icon--volume-high,
.media-button--fullscreen .media-icon--fullscreen-enter,
.media-button--fullscreen .media-icon--fullscreen-exit,
.media-button--pip .media-icon--pip-enter,
.media-button--pip .media-icon--pip-exit,
.media-button--cast .media-icon--cast-enter,
.media-button--cast .media-icon--cast-exit,
.media-button--airplay .media-icon--airplay-enter,
.media-button--airplay .media-icon--airplay-exit,
.media-button--captions .media-icon--captions-off,
.media-button--captions .media-icon--captions-on {
display: none;
opacity: 0;
}
/* --- Active icon per state --- */
/* Play: ended → restart */
.media-button--play[data-ended] .media-icon--restart,
/* Play: paused or not yet started (not ended) → play */
.media-button--play:not([data-ended])[data-paused] .media-icon--play,
.media-button--play:not([data-ended]):not([data-started]) .media-icon--play,
/* Play: started and not paused/ended → pause */
.media-button--play[data-started]:not([data-paused]):not([data-ended]) .media-icon--pause,
/* Mute: muted → volume off */
.media-button--mute[data-muted] .media-icon--volume-off,
/* Mute: volume low (not muted) → volume low */
.media-button--mute:not([data-muted])[data-volume-level="low"] .media-icon--volume-low,
/* Mute: volume high (not muted, not low) → volume high */
.media-button--mute:not([data-muted]):not([data-volume-level="low"]) .media-icon--volume-high,
/* Fullscreen: not fullscreen → enter */
.media-button--fullscreen:not([data-fullscreen]) .media-icon--fullscreen-enter,
/* Fullscreen: fullscreen → exit */
.media-button--fullscreen[data-fullscreen] .media-icon--fullscreen-exit,
/* Picture-in-Picture: not active → enter */
.media-button--pip:not([data-pip]) .media-icon--pip-enter,
/* Picture-in-Picture: active → exit */
.media-button--pip[data-pip] .media-icon--pip-exit,
/* Cast: not connected → enter */
.media-button--cast:not([data-cast-state="connected"]) .media-icon--cast-enter,
/* Cast: connected → exit */
.media-button--cast[data-cast-state="connected"] .media-icon--cast-exit,
/* AirPlay: not connected → enter */
.media-button--airplay:not([data-airplay-state="connected"]) .media-icon--airplay-enter,
/* AirPlay: connected → exit */
.media-button--airplay[data-airplay-state="connected"] .media-icon--airplay-exit,
/* Captions: not active → captions off */
.media-button--captions:not([data-active]) .media-icon--captions-off,
/* Captions: active → captions on */
.media-button--captions[data-active] .media-icon--captions-on {
display: block;
opacity: 1;
}
/* --- Pause keyframe animations on inactive icons --- */
/* The airplay-exit SVG defines its keyframes against CSS variables (mirroring
the spinner pattern). When the AirPlay session isn't active the SVG is
still in the DOM — just `display: none` — so its animations would keep
running. Set the variables to `none` to short-circuit the keyframes. */
.media-button--airplay:not([data-airplay-state="connected"]) {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
/* -------------------------------------------------------------------------- */
/* Global @keyframes for all video skins (CSS & Tailwind) */
/* -------------------------------------------------------------------------- */
@keyframes media-shake {
0%,
100% {
translate: 0 0;
}
20% {
translate: -6px 0;
}
40% {
translate: 4px 0;
}
60% {
translate: -2px 0;
}
80% {
translate: 1px 0;
}
}
/* -------------------------------------------------------------------------- */
/* Global @properties for all video skins (CSS & Tailwind) */
/* -------------------------------------------------------------------------- */
@property --media-progress-fill {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
/* ==========================================================================
Root
========================================================================== */
.media-minimal-skin--video {
--media-border-color: oklch(0 0 0 / 0.15);
--media-video-border-radius: var(--media-border-radius, 0.75rem);
--media-controls-background-color: transparent;
--media-controls-transition-duration: 100ms;
--media-controls-transition-timing-function: ease-out;
--media-error-dialog-transition-duration: 150ms;
--media-error-dialog-transition-delay: 100ms;
--media-error-dialog-transition-timing-function: ease-out;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
--media-tooltip-background-color: oklch(0 0 0 / 0.5);
--media-tooltip-border-color: oklch(1 0 0 / 0.1);
--media-tooltip-backdrop-filter: blur(16px) saturate(1.5);
--media-tooltip-text-color: currentColor;
--media-tooltip-side-offset: 0.5rem;
--media-tooltip-boundary-offset: 0.5rem;
--media-popover-background-color: var(--media-tooltip-background-color);
--media-popover-border-color: var(--media-tooltip-border-color);
--media-popover-backdrop-filter: var(--media-tooltip-backdrop-filter);
--media-popover-side-offset: 1.5rem;
--media-popover-boundary-offset: var(--media-tooltip-boundary-offset);
overflow: clip;
background: oklch(0 0 0);
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-color-scheme: dark) {
--media-border-color: oklch(1 0 0 / 0.15);
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-controls-background-color: oklch(0 0 0);
--media-tooltip-background-color: oklch(0 0 0);
}
@container media-root (width > 42rem) {
& > * {
--media-popover-side-offset: 0.5rem;
}
}
&:has(.media-controls:not([data-visible])) {
/* Slight delay to hide controls on non-touch devices after interaction */
@media (pointer: fine) {
--media-controls-transition-duration: 300ms;
}
@media (pointer: coarse) {
--media-controls-transition-duration: 150ms;
}
@media (prefers-reduced-motion: reduce) {
--media-controls-transition-duration: 50ms;
}
}
/* Inner border ring */
&::after {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
content: "";
border-radius: inherit;
box-shadow: inset 0 0 0 1px var(--media-border-color);
}
/* Fullscreen */
&:fullscreen {
--media-border-radius: 0;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-minimal-skin--video .media-error {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
outline: none;
}
.media-minimal-skin--video .media-error__dialog {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 16rem;
padding: 1rem;
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.5);
pointer-events: auto;
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: var(--media-error-dialog-transition-timing-function);
transition-duration: var(--media-error-dialog-transition-duration);
transition-property: opacity, scale;
}
.media-minimal-skin--video .media-error[data-starting-style] .media-error__dialog,
.media-minimal-skin--video .media-error[data-ending-style] .media-error__dialog {
opacity: 0;
scale: 0.5;
}
.media-minimal-skin--video .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
.media-minimal-skin--video .media-error__content {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.375rem 0;
}
.media-minimal-skin--video .media-error__title {
font-size: 1.125rem;
}
.media-minimal-skin--video .media-error[data-open] ~ .media-controls {
display: none;
}
/* ==========================================================================
Controls (hide/show behavior)
========================================================================== */
.media-minimal-skin--video .media-controls {
position: absolute;
inset-inline: 0.25rem;
bottom: 0.25rem;
z-index: 10;
flex-wrap: wrap;
column-gap: 0.5rem;
padding: 0.25rem;
color: oklch(1 0 0);
border-radius: 0.75rem;
transition-timing-function: var(--media-controls-transition-timing-function);
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) {
transition-property: translate, filter, opacity;
will-change: translate, filter, opacity;
}
@media (pointer: coarse) {
transition-property: translate, opacity;
will-change: translate, opacity;
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 100%;
}
}
& .media-time-controls {
flex: 0 0 100%;
order: -1;
padding-inline: 0.625rem;
}
& .media-button-group:first-child {
flex: 1;
text-align: left;
}
& .media-button-group:last-child {
flex: 1;
justify-content: end;
}
@container media-root (width > 42rem) {
inset-inline: 0.5rem;
bottom: 0.5rem;
flex-wrap: nowrap;
& .media-time-controls {
flex: 1;
order: unset;
}
& .media-button-group:first-child,
& .media-button-group:last-child {
flex: 0 0 auto;
}
}
}
/* Hide cursor when controls are hidden */
.media-minimal-skin--video:has(.media-controls:not([data-visible])) {
cursor: none;
}
/* ==========================================================================
Sliders
========================================================================== */
.media-minimal-skin--video .media-slider__track {
box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);
}
/* ==========================================================================
Popups & Animations
========================================================================== */
.media-minimal-skin--video .media-popover--volume {
padding-block: 0.75rem;
background: transparent;
border-radius: 0.75rem;
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
background: var(--media-controls-background-color);
}
@container media-root (width > 42rem) {
& > * {
--media-popover-side-offset: 0rem;
}
}
}
/* ==========================================================================
Slider preview
========================================================================== */
.media-minimal-skin--video .media-slider__thumbnail {
--media-slider-thumbnail-max-width: 11rem;
--media-slider-thumbnail-max-height: 8rem;
--media-slider-thumbnail-padding: -0.5rem;
/**
Inset is the difference between the container width and the slider (100%) width.
We only add to the end as we render the time there.
*/
--media-slider-thumbnail-inset: calc(100cqi - 100%);
position: absolute;
bottom: 100%;
left: clamp(
calc(var(--media-slider-thumbnail-max-width) / 2 + var(--media-slider-thumbnail-padding)),
var(--media-slider-pointer),
calc(
100% -
var(--media-slider-thumbnail-max-width) /
2 -
var(--media-slider-thumbnail-padding) +
var(--media-slider-thumbnail-inset)
)
);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: scale, opacity, filter;
@container media-root (width > 42rem) {
left: var(--media-slider-pointer);
}
& .media-thumbnail__image-wrapper {
position: relative;
&::after {
position: absolute;
inset: 0;
content: "";
border-radius: inherit;
box-shadow:
0 0 0 1px oklch(0 0 0 / 0.05),
0 1px 3px 0 oklch(0 0 0 / 0.2),
0 1px 2px -1px oklch(0 0 0 / 0.2);
}
}
& .media-thumbnail__image {
max-width: var(--media-slider-thumbnail-max-width);
max-height: var(--media-slider-thumbnail-max-height);
}
}
.media-minimal-skin--video .media-slider[data-pointing] .media-slider__thumbnail:has([role="img"]:not([data-hidden])) {
opacity: 1;
filter: blur(0);
scale: 1;
}
.media-minimal-skin--video
.media-slider__thumbnail:has([role="img"]:not([data-hidden]))
+ .media-slider__preview
.media-slider__value {
display: none;
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/video-minimal-ui.js"></script>
<link rel="stylesheet" href="./player.css">
<video-player>
<media-container class="**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,0.75rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 scrollbar-thin scrollbar-thumb-current/30 [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-auto [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-thumb-current/80 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] bg-black overflow-clip after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10 after:inset-0 after:ring-1 after:ring-inset after:ring-black/15 dark:after:ring-white/15 [&_::slotted(video)]:block [&_::slotted(video)]:w-full [&_::slotted(video)]:h-full [&_::slotted(video)]:rounded-(--media-video-border-radius) [&_::slotted(video)]:[object-fit:var(--media-object-fit,cover)] [&_::slotted(video)]:[object-position:var(--media-object-position,center)] [--media-video-border-radius:var(--media-border-radius,0.75rem)] [--media-controls-background-color:transparent] [--media-controls-transition-duration:100ms] [--media-controls-transition-timing-function:ease-out] [--media-error-dialog-transition-duration:150ms] [--media-error-dialog-transition-delay:100ms] [--media-error-dialog-transition-timing-function:ease-out] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-tooltip-background-color:oklch(0_0_0/0.5)] [--media-tooltip-border-color:oklch(1_0_0/0.1)] [--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)] [--media-tooltip-text-color:currentColor] [--media-tooltip-side-offset:0.5rem] [--media-tooltip-boundary-offset:0.5rem] [--media-popover-background-color:var(--media-tooltip-background-color)] [--media-popover-border-color:var(--media-tooltip-border-color)] [--media-popover-backdrop-filter:var(--media-tooltip-backdrop-filter)] [--media-popover-side-offset:1.5rem] [--media-popover-boundary-offset:var(--media-tooltip-boundary-offset)] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-popup-transition-duration:0ms] [@media(prefers-reduced-transparency:reduce)]:[--media-controls-background-color:oklch(0_0_0)] contrast-more:[--media-controls-background-color:oklch(0_0_0)] [@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(0_0_0)] contrast-more:[--media-tooltip-background-color:oklch(0_0_0)] @2xl/media-root:*:[--media-popover-side-offset:0.5rem] pointer-fine:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:300ms] pointer-coarse:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:150ms] motion-reduce:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:50ms] [--media-caption-track-y:-0.5rem] [--media-caption-track-delay:25ms] [--media-caption-track-duration:var(--media-controls-transition-duration)] has-[[data-controls][data-visible]]:[--media-caption-track-y:-5rem] @2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:-3rem] [&:fullscreen]:[--media-border-radius:0] [&:fullscreen_::slotted(video)]:object-contain">
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" playsinline></video>
<media-poster class="absolute inset-0 w-full h-full pointer-events-none transition-opacity duration-250 not-data-visible:opacity-0 before:absolute before:inset-0 before:pointer-events-none before:[background-image:var(--media-poster-placeholder,none)] before:bg-no-repeat before:[background-position:var(--media-object-position,center)] before:[background-size:var(--media-object-fit,contain)] before:[filter:blur(var(--media-poster-placeholder-blur,20px))] [&_::slotted(img)]:absolute [&_::slotted(img)]:inset-0 [&_::slotted(img)]:w-full [&_::slotted(img)]:h-full [&_::slotted(img)]:[object-fit:var(--media-object-fit,contain)] [&_::slotted(img)]:[object-position:var(--media-object-position,center)] [&_::slotted(img)]:rounded-(--media-video-border-radius)">
<img src="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp" />
</media-poster>
<media-buffering-indicator class="peer/buffering absolute inset-0 z-10 hidden place-content-center pointer-events-none text-white not-data-visible:[--media-spinner-animation:none] data-visible:grid">
<media-icon name="spinner" family="minimal"></media-icon>
</media-buffering-indicator>
<media-error-dialog class="peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none pointer-events-none outline-none">
<div class="flex flex-col gap-3 max-w-64 p-4 text-white text-shadow-2xs text-shadow-black/50 transition-[opacity,scale,transform] duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) ease-(--media-error-dialog-transition-timing-function) group-data-starting-style/error:opacity-0 group-data-starting-style/error:scale-50 group-data-ending-style/error:opacity-0 group-data-ending-style/error:scale-50 group-data-ending-style/error:delay-0 pointer-events-auto">
<div class="flex flex-col gap-2 py-1.5">
<media-alert-dialog-title class="font-semibold leading-tight text-lg">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="opacity-70 wrap-anywhere"></media-alert-dialog-description>
</div>
<div class="flex gap-2 *:flex-1">
<media-alert-dialog-close class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-white text-black font-medium text-shadow-none">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<media-controls data-controls="" class="peer/controls @container/media-controls flex items-center bg-(--media-controls-background-color) [backdrop-filter:var(--media-controls-backdrop-filter)] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) absolute bottom-1 inset-x-1 p-1 gap-x-2 flex-wrap rounded-xl text-white z-10 peer-data-open/error:hidden ease-(--media-controls-transition-timing-function) duration-(--media-controls-transition-duration) pointer-fine:will-change-[translate,filter,opacity] pointer-fine:transition-[translate,filter,opacity] pointer-coarse:will-change-[translate,opacity] pointer-coarse:transition-[translate,opacity] not-data-visible:opacity-0 not-data-visible:pointer-events-none motion-safe:not-data-visible:translate-y-full pointer-fine:motion-safe:not-data-visible:blur-sm @2xl/media-root:flex-nowrap @2xl/media-root:bottom-2 @2xl/media-root:inset-x-2 @2xl/media-root:*:[--media-popover-side-offset:0rem]">
<media-tooltip-group>
<div class="flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 @2xl/media-root:flex-none">
<media-play-button commandfor="play-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 group">
<media-icon name="restart" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-ended:block group-data-ended:opacity-100"></media-icon>
<media-icon name="play" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100"></media-icon>
<media-icon name="pause" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100"></media-icon>
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-backward-tooltip" seconds="-10" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90">
<span class="relative grid">
<media-icon name="seek" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out [scale:-1_1]"></media-icon>
<span class="text-[10px] font-medium tracking-tighter tabular-nums absolute -left-px -bottom-0.75">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-backward-tooltip" side="top" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-forward-tooltip" seconds="10" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90">
<span class="relative grid">
<media-icon name="seek" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
<span class="text-[10px] font-medium tracking-tighter tabular-nums absolute -right-px -bottom-0.75">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-forward-tooltip" side="top" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
</div>
<div class="@container flex flex-row-reverse items-center flex-1 gap-3 @2xl/media-root:flex-row grow-0 shrink-0 basis-full order-[-1] px-2.5 @2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]">
<media-time-group class="flex items-center gap-1">
<media-time toggle type="current" class="hidden tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out supports-[corner-shape:squircle]:rounded-4 supports-[corner-shape:squircle]:[corner-shape:squircle] focus-visible:outline-current focus-visible:outline-offset-2 @2xl/media-root:inline"></media-time>
<media-time-separator class="hidden @2xl/media-root:inline @2xl/media-root:text-current/60"></media-time-separator>
<media-time type="duration" class="tabular-nums @2xl/media-root:text-current/60"></media-time>
</media-time-group>
<media-time-slider class="group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]">
<media-slider-track class="relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-0.75 data-[orientation=vertical]:w-0.75 data-[orientation=vertical]:h-full ring-1 ring-black/5">
<media-slider-fill class="absolute rounded-[inherit] pointer-events-none bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)"></media-slider-fill>
<media-slider-buffer class="absolute rounded-[inherit] pointer-events-none bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer,0) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)"></media-slider-buffer>
</media-slider-track>
<media-slider-thumb class="z-10 absolute size-3 -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,scale,outline-offset] duration-150 ease-out select-none outline-2 outline-transparent -outline-offset-2 focus-visible:outline-current focus-visible:outline-offset-2 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill,0))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))] opacity-0 scale-70 origin-center group-hover/slider:opacity-100 group-hover/slider:scale-100 group-focus-within/slider:opacity-100 group-focus-within/slider:scale-100"></media-slider-thumb>
<div class="group/thumbnail peer/thumbnail pointer-events-none [--media-slider-thumbnail-max-width:11rem] [--media-slider-thumbnail-max-height:8rem] [--media-slider-thumbnail-padding:-0.5rem] [--media-slider-thumbnail-inset:calc(100cqi-100%)] absolute [left:clamp(calc(var(--media-slider-thumbnail-max-width)/2+var(--media-slider-thumbnail-padding)),var(--media-slider-pointer),calc(100%-var(--media-slider-thumbnail-max-width)/2-var(--media-slider-thumbnail-padding)+var(--media-slider-thumbnail-inset)))] bottom-full -translate-x-1/2 @2xl/media-root:[left:var(--media-slider-pointer)] opacity-0 scale-80 blur-sm origin-bottom transition-[scale,opacity,filter] duration-150 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:opacity-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:scale-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:blur-none">
<div class="relative rounded-lg bg-black/90 after:absolute after:inset-0 after:rounded-[inherit] after:ring-1 after:ring-black/5 after:shadow-sm after:shadow-black/20">
<media-slider-thumbnail class="block rounded-[inherit] transition-opacity duration-150 ease-out data-loading:opacity-0 max-w-(--media-slider-thumbnail-max-width) max-h-(--media-slider-thumbnail-max-height)"></media-slider-thumbnail>
</div>
<media-slider-value type="pointer" class="hidden tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out supports-[corner-shape:squircle]:rounded-4 supports-[corner-shape:squircle]:[corner-shape:squircle] focus-visible:outline-current focus-visible:outline-offset-2 @2xl/media-root:inline mt-2 block text-center tabular-nums"></media-slider-value>
<media-icon name="spinner" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity duration-150 ease-out group-not-has-[[role=img][data-loading]]/thumbnail:[--media-spinner-animation:none] group-has-[[role=img][data-loading]]/thumbnail:opacity-100"></media-icon>
</div>
<media-slider-preview class="group/preview before:block before:bg-current/35 before:opacity-0 before:scale-50 before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 data-[orientation=horizontal]:before:min-w-px data-[orientation=horizontal]:before:h-5 data-[orientation=vertical]:before:w-5 data-[orientation=vertical]:before:min-h-px peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden">
<media-slider-value type="pointer" class="absolute bottom-6 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none hidden tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out supports-[corner-shape:squircle]:rounded-4 supports-[corner-shape:squircle]:[corner-shape:squircle] focus-visible:outline-current focus-visible:outline-offset-2 @2xl/media-root:inline"></media-slider-value>
</media-slider-preview>
</media-time-slider>
</div>
<div class="flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 justify-end @2xl/media-root:flex-none group/settings">
<media-mute-button commandfor="video-volume-popover" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 group">
<media-icon name="volume-off" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-muted:block group-data-muted:opacity-100"></media-icon>
<media-icon name="volume-low" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100"></media-icon>
<media-icon name="volume-high" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100"></media-icon>
</media-mute-button>
<media-popover id="video-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) py-3 px-0 bg-transparent rounded-xl [@media(prefers-reduced-transparency:reduce)]:bg-(--media-controls-background-color) contrast-more:bg-(--media-controls-background-color)">
<media-volume-slider class="group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-0.75 data-[orientation=vertical]:w-0.75 data-[orientation=vertical]:h-full ring-1 ring-black/5">
<media-slider-fill class="absolute rounded-[inherit] pointer-events-none bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="z-10 absolute size-3 -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,scale,outline-offset] duration-150 ease-out select-none outline-2 outline-transparent -outline-offset-2 focus-visible:outline-current focus-visible:outline-offset-2 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill,0))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))]"></media-slider-thumb>
</media-volume-slider>
</media-popover>
<button commandfor="settings-menu" aria-label="Settings" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 group hidden group-has-[[data-availability=available]]/settings:grid media-button--settings">
<media-icon name="gear" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0"></media-icon>
</button>
<media-menu id="settings-menu" side="top" align="center" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.5rem] [--menu-border-radius:1rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none [--media-popup-transition:var(--media-popup-base-transition),height_var(--media-popup-transition-timing-function)_var(--menu-transition-duration),width_var(--media-popup-transition-timing-function)_var(--menu-transition-duration)] data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)] data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)] min-w-48 w-(--media-menu-width) h-(--media-menu-height) !overflow-hidden [--media-popover-side-offset:1.5rem] [--media-popover-boundary-offset:0.5rem] @2xl/media-root:[--media-popover-side-offset:0.5rem]">
<media-menu-view class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] group/menu-root-view data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur">
<div class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<media-menu-item commandfor="settings-quality-menu" type="quality" data-setting="quality" class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50 media-menu__item--submenu">
<media-icon name="switches" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
<span>Quality</span>
<span class="ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65">
<media-menu-item-value class="max-w-24 overflow-hidden text-ellipsis whitespace-nowrap"></media-menu-item-value>
<media-icon name="chevron" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-audio-menu" type="audio-track" data-setting="audio-track" class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50 media-menu__item--submenu">
<media-icon name="speech" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
<span>Audio</span>
<span class="ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65">
<media-menu-item-value class="max-w-24 overflow-hidden text-ellipsis whitespace-nowrap"></media-menu-item-value>
<media-icon name="chevron" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out size-3.5"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-speed-menu" type="playback-rate" data-setting="playback-rate" class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50 media-menu__item--submenu">
<media-icon name="speed" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
<span>Speed</span>
<span class="ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65">
<media-menu-item-value class="max-w-24 overflow-hidden text-ellipsis whitespace-nowrap"></media-menu-item-value>
<media-icon name="chevron" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-captions-menu" type="captions" data-setting="captions" class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50 media-menu__item--submenu">
<media-icon name="captions-off" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
<span>Captions</span>
<span class="ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65">
<media-menu-item-value class="max-w-24 overflow-hidden text-ellipsis whitespace-nowrap"></media-menu-item-value>
<media-icon name="chevron" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5"></media-icon>
</span>
</media-menu-item>
</div>
</media-menu-view>
<media-menu id="settings-quality-menu" class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full">
<media-menu-back class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full">
<media-icon name="chevron" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5 [scale:-1_1]"></media-icon>
Quality
</media-menu-back>
<div class="my-1 border-b border-[oklch(1_0_0/0.1)]"></div>
<media-quality-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span>
<span data-part="label"></span>
<sup data-part="tier" class="pl-0.5 pt-px text-[0.5625rem] font-semibold leading-none text-current/70"></sup>
</span>
<span data-part="badge" class="rounded-full bg-current/10 px-1.5 py-0.5 text-[0.6875rem] font-medium leading-none text-current/70"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-quality-radio-group>
</media-menu>
<media-menu id="settings-audio-menu" class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full">
<media-menu-back class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full">
<media-icon name="chevron" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out size-3.5 [scale:-1_1]"></media-icon>
Audio
</media-menu-back>
<div class="my-1 border-b border-[oklch(1_0_0/0.1)]"></div>
<media-audio-track-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-audio-track-radio-group>
</media-menu>
<media-menu id="settings-speed-menu" class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full">
<media-menu-back class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full">
<media-icon name="chevron" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5 [scale:-1_1]"></media-icon>
Speed
</media-menu-back>
<div class="my-1 border-b border-[oklch(1_0_0/0.1)]"></div>
<media-playback-rate-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
</media-menu>
<media-menu id="settings-captions-menu" class="absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full">
<media-menu-back class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full">
<media-icon name="chevron" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] size-3.5 [scale:-1_1]"></media-icon>
Captions
</media-menu-back>
<div class="my-1 border-b border-[oklch(1_0_0/0.1)]"></div>
<media-captions-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-captions-radio-group>
</media-menu>
</media-menu>
<media-cast-button commandfor="cast-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 group">
<media-icon name="cast-enter" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100"></media-icon>
<media-icon name="cast-exit" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100"></media-icon>
</media-cast-button>
<media-tooltip id="cast-tooltip" side="top" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-airplay-button commandfor="airplay-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]">
<media-icon name="airplay-enter" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100"></media-icon>
<media-icon name="airplay-exit" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100"></media-icon>
</media-airplay-button>
<media-tooltip id="airplay-tooltip" side="top" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-pip-button commandfor="pip-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 group">
<media-icon name="pip-enter" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100"></media-icon>
<media-icon name="pip-exit" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-pip:block group-data-pip:opacity-100"></media-icon>
</media-pip-button>
<media-tooltip id="pip-tooltip" side="top" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-fullscreen-button commandfor="fullscreen-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 group">
<media-icon name="fullscreen-enter" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100"></media-icon>
<media-icon name="fullscreen-exit" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100"></media-icon>
</media-fullscreen-button>
<media-tooltip id="fullscreen-tooltip" side="top" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
</div>
</media-tooltip-group>
</media-controls>
<div class="absolute inset-0 flex flex-col items-start pointer-events-none rounded-[inherit] opacity-0 bg-linear-to-t from-black/70 via-black/50 via-[7.5rem] to-transparent backdrop-blur-none backdrop-saturate-100 transition-[opacity,backdrop-filter] duration-(--media-controls-transition-duration) ease-out peer-data-visible/controls:opacity-100 peer-data-visible/buffering:bg-black/35 peer-data-visible/buffering:opacity-100 peer-data-visible/buffering:backdrop-blur-sm peer-data-open/error:opacity-100 peer-data-open/error:duration-(--media-error-dialog-transition-duration) peer-data-open/error:delay-(--media-error-dialog-transition-delay) peer-data-open/error:backdrop-blur-lg peer-data-open/error:backdrop-saturate-120"></div>
<!-- Hotkeys -->
<media-hotkey keys="Space" action="togglePaused"></media-hotkey>
<media-hotkey keys="k" action="togglePaused"></media-hotkey>
<media-hotkey keys="m" action="toggleMuted"></media-hotkey>
<media-hotkey keys="f" action="toggleFullscreen"></media-hotkey>
<media-hotkey keys="c" action="toggleSubtitles"></media-hotkey>
<media-hotkey keys="i" action="togglePictureInPicture"></media-hotkey>
<media-hotkey keys="ArrowRight" action="seekStep" value="5"></media-hotkey>
<media-hotkey keys="ArrowLeft" action="seekStep" value="-5"></media-hotkey>
<media-hotkey keys="l" action="seekStep" value="10"></media-hotkey>
<media-hotkey keys="j" action="seekStep" value="-10"></media-hotkey>
<media-hotkey keys="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
<media-hotkey keys="0-9" action="seekToPercent"></media-hotkey>
<media-hotkey keys="Home" action="seekToPercent" value="0"></media-hotkey>
<media-hotkey keys="End" action="seekToPercent" value="100"></media-hotkey>
<media-hotkey keys=">" action="speedUp"></media-hotkey>
<media-hotkey keys="<" action="speedDown"></media-hotkey>
<!-- Gestures -->
<media-gesture type="tap" action="togglePaused" pointer="mouse" region="center"></media-gesture>
<media-gesture type="tap" action="toggleControls" pointer="touch"></media-gesture>
<media-gesture type="doubletap" action="seekStep" value="-10" region="left"></media-gesture>
<media-gesture type="doubletap" action="toggleFullscreen" region="center"></media-gesture>
<media-gesture type="doubletap" action="seekStep" value="10" region="right"></media-gesture>
<!-- Input Feedback -->
<media-status-announcer></media-status-announcer>
<div class="absolute inset-x-0 top-0 bottom-14 pointer-events-none grid grid-cols-3 items-center justify-items-center overflow-hidden rounded-[inherit] @2xl/media-root:bottom-0 [color:var(--media-color-primary,oklch(1_0_0))]">
<media-volume-indicator
hidden
class="group/input-indicator absolute top-0 inset-x-0 pt-3 pb-32 flex justify-center text-inherit font-medium origin-top pointer-events-none duration-100 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-400 data-starting-style:ease-in data-ending-style:duration-400 data-ending-style:ease-in [background-image:linear-gradient(to_bottom,oklch(0_0_0/0.35),oklch(0_0_0/0.2)_3rem,oklch(0_0_0/0))] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) pointer-fine:will-change-[translate,filter,opacity] pointer-fine:transition-[translate,filter,opacity] pointer-coarse:will-change-[translate,opacity] pointer-coarse:transition-[translate,opacity] pointer-fine:motion-safe:data-starting-style:blur-sm pointer-fine:motion-safe:data-ending-style:blur-sm motion-safe:data-ending-style:-translate-y-full *:data-feedback-island-content:w-[min(80%,14rem)] data-open:duration-100 data-min:*:data-feedback-island-content:animate-media-shake data-max:*:data-feedback-island-content:animate-media-shake motion-reduce:data-min:*:data-feedback-island-content:animate-none motion-reduce:data-max:*:data-feedback-island-content:animate-none"
>
<media-volume-indicator-fill data-feedback-island-content="" class="flex justify-between items-center gap-2 px-2.5 py-1 *:last:ml-auto [@media(prefers-reduced-transparency:reduce)]:bg-(--media-controls-background-color) [@media(prefers-reduced-transparency:reduce)]:rounded-lg contrast-more:bg-(--media-controls-background-color) contrast-more:rounded-lg">
<media-icon name="volume-high" family="minimal" class="hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] group-data-[level=high]/input-indicator:block"></media-icon>
<media-icon name="volume-low" family="minimal" class="hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] group-data-[level=low]/input-indicator:block"></media-icon>
<media-icon name="volume-off" family="minimal" class="hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] group-data-[level=off]/input-indicator:block"></media-icon>
<div aria-hidden="true" class="[--media-progress-fill:var(--media-volume-fill)] w-full h-0.75 rounded-full [background-image:linear-gradient(to_right,currentColor_0%,currentColor_var(--media-progress-fill),oklch(from_currentColor_l_c_h/0.2)_var(--media-progress-fill),oklch(from_currentColor_l_c_h/0.2)_100%)] shadow-[0_1px_0_var(--media-current-shadow-color-subtle)]"></div>
<media-volume-indicator-value class="ml-auto"></media-volume-indicator-value>
</media-volume-indicator-fill>
</media-volume-indicator>
<media-status-indicator hidden actions="toggleSubtitles toggleFullscreen togglePictureInPicture" class="group/input-indicator absolute top-0 inset-x-0 pt-3 pb-32 flex justify-center text-inherit font-medium origin-top pointer-events-none duration-100 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-400 data-starting-style:ease-in data-ending-style:duration-400 data-ending-style:ease-in [background-image:linear-gradient(to_bottom,oklch(0_0_0/0.35),oklch(0_0_0/0.2)_3rem,oklch(0_0_0/0))] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) pointer-fine:will-change-[translate,filter,opacity] pointer-fine:transition-[translate,filter,opacity] pointer-coarse:will-change-[translate,opacity] pointer-coarse:transition-[translate,opacity] pointer-fine:motion-safe:data-starting-style:blur-sm pointer-fine:motion-safe:data-ending-style:blur-sm motion-safe:data-ending-style:-translate-y-full data-open:duration-100">
<div class="flex justify-between items-center gap-2 px-2.5 py-1 *:last:ml-auto [@media(prefers-reduced-transparency:reduce)]:bg-(--media-controls-background-color) [@media(prefers-reduced-transparency:reduce)]:rounded-lg contrast-more:bg-(--media-controls-background-color) contrast-more:rounded-lg">
<media-icon name="captions-on" family="minimal" class="hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] group-data-[status=captions-on]/input-indicator:block"></media-icon>
<media-icon name="captions-off" family="minimal" class="hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] group-data-[status=captions-off]/input-indicator:block"></media-icon>
<media-icon name="fullscreen-enter" family="minimal" class="hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] group-data-[status=fullscreen]/input-indicator:block"></media-icon>
<media-icon name="fullscreen-exit" family="minimal" class="hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] group-data-[status=exit-fullscreen]/input-indicator:block"></media-icon>
<media-icon name="pip-enter" family="minimal" class="hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] group-data-[status=pip]/input-indicator:block"></media-icon>
<media-icon name="pip-exit" family="minimal" class="hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] group-data-[status=exit-pip]/input-indicator:block"></media-icon>
<media-status-indicator-value class="ml-auto"></media-status-indicator-value>
</div>
</media-status-indicator>
<media-seek-indicator hidden class="group/input-indicator col-start-2 row-start-1 grid place-content-center text-center p-4 data-direction:gap-1 @2xl/media-root:data-direction:p-6 not-data-direction:[transition-property:opacity,scale] not-data-direction:duration-200 not-data-direction:ease-out motion-reduce:not-data-direction:transition-opacity motion-reduce:not-data-direction:duration-50 not-data-direction:data-starting-style:opacity-0 not-data-direction:data-ending-style:opacity-0 not-data-direction:data-starting-style:scale-[0.85] not-data-direction:data-ending-style:scale-[0.85] not-data-direction:data-ending-style:duration-100 not-data-direction:data-ending-style:ease-in data-[direction=backward]:col-start-1 data-[direction=backward]:justify-self-start data-[direction=forward]:col-start-3 data-[direction=forward]:justify-self-end">
<media-icon name="chevron" family="minimal" class="hidden size-[calc(var(--media-icon-size)*2)] group-data-direction/input-indicator:size-[calc(var(--media-icon-size)*1.5)] group-data-direction/input-indicator:block group-data-[direction=backward]/input-indicator:[scale:-1_1] motion-safe:transition-[translate,opacity] motion-safe:duration-200 motion-safe:ease-in-out motion-safe:group-data-starting-style/input-indicator:opacity-0 motion-safe:group-data-ending-style/input-indicator:opacity-0 motion-safe:group-data-[direction=forward]/input-indicator:group-data-starting-style/input-indicator:[translate:-60%_0] motion-safe:group-data-[direction=backward]/input-indicator:group-data-starting-style/input-indicator:[translate:60%_0]"></media-icon>
<media-seek-indicator-value class="tabular-nums"></media-seek-indicator-value>
</media-seek-indicator>
<media-status-indicator hidden actions="togglePaused" class="group/input-indicator col-start-2 row-start-1 grid place-content-center text-center p-4 data-direction:gap-1 @2xl/media-root:data-direction:p-6 not-data-direction:[transition-property:opacity,scale] not-data-direction:duration-200 not-data-direction:ease-out motion-reduce:not-data-direction:transition-opacity motion-reduce:not-data-direction:duration-50 not-data-direction:data-starting-style:opacity-0 not-data-direction:data-ending-style:opacity-0 not-data-direction:data-starting-style:scale-[0.85] not-data-direction:data-ending-style:scale-[0.85] not-data-direction:data-ending-style:duration-100 not-data-direction:data-ending-style:ease-in data-[direction=backward]:col-start-1 data-[direction=backward]:justify-self-start data-[direction=forward]:col-start-3 data-[direction=forward]:justify-self-end">
<media-icon name="play" family="minimal" class="hidden size-[calc(var(--media-icon-size)*2)] group-data-[status=play]/input-indicator:block"></media-icon>
<media-icon name="pause" family="minimal" class="hidden size-[calc(var(--media-icon-size)*2)] group-data-[status=pause]/input-indicator:block"></media-icon>
</media-status-indicator>
</div>
</media-container>
</video-player>
Minimal Audio Skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { createPlayer, Container, usePlayer, BufferingIndicator, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster, type RenderProp } from '@videojs/react';
import { Audio, audioFeatures } from '@videojs/react/audio';
import './player.css';
function PlaybackRateRadioGroup(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
const { options, setValue, value } = state;
return (
<Menu.RadioGroup className="media-menu__group" value={value} onValueChange={setValue} aria-label="Playback rate">
{options.map((option) => (
<Menu.RadioItem key={option.value} className="media-menu__item" value={option.value} disabled={option.disabled}>
<span>{option.label}</span>
<Menu.ItemIndicator checked={option.value === value} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
);
}
function PlaybackRateTrigger(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
return (
<Menu.Trigger
disabled={state.disabled}
render={<PlaybackRateButton className="media-button--playback-rate" render={<Button />} />}
/>
);
}
// ================================================================
// Player
// ================================================================
export const Player = createPlayer({ features: audioFeatures });
export interface AudioPlayerProps {
src: string;
style?: CSSProperties;
className?: string;
}
const SEEK_TIME = 10;
/**
* @example
* ```tsx
* <AudioPlayer
* src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
* />
* ```
*/
export function AudioPlayer({ src, className, ...rest }: AudioPlayerProps): ReactNode {
return (
<Player.Provider>
<Container className={`media-minimal-skin media-minimal-skin--audio ${className ?? ''}`} {...rest}>
<Audio src={src} />
<ErrorDialog.Root>
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title">Something went wrong.</ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--subtle">OK</ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<div className="media-controls">
<Tooltip.Provider>
<div className="media-button-group">
<span className="media-button--play__wrapper">
<BufferingIndicator
render={(props) => (
<div {...props} className="media-buffering-indicator">
<SpinnerIcon className="media-icon" />
</div>
)}
/>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<PlayButton className="media-button--play" render={<Button />}>
<RestartIcon className="media-icon media-icon--restart" />
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</PlayButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
</span>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek media-icon--flipped" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} className="media-button--seek" render={<Button />}>
<span className="media-icon__container">
<SeekIcon className="media-icon media-icon--seek" />
<span className="media-icon__label">{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
</div>
<div className="media-time-controls">
<Time.Group className="media-time-group">
<Time.Value toggle type="current" className="media-time media-time--current" />
<Time.Separator className="media-time-separator" />
<Time.Value type="duration" className="media-time media-time--duration" />
</Time.Group>
<TimeSlider.Root className="media-slider">
<TimeSlider.Track className="media-slider__track">
<TimeSlider.Fill className="media-slider__fill" />
<TimeSlider.Buffer className="media-slider__buffer" />
</TimeSlider.Track>
<TimeSlider.Thumb className="media-slider__thumb" />
<TimeSlider.Preview className="media-slider__preview">
<TimeSlider.Value type="pointer" className="media-slider__value media-time" />
</TimeSlider.Preview>
</TimeSlider.Root>
</div>
<div className="media-button-group">
<Menu.Root side="top" align="center" boundary="viewport">
<PlaybackRateTrigger />
<Menu.Content className="media-popover media-menu media-menu--playback-rate">
<PlaybackRateRadioGroup />
</Menu.Content>
</Menu.Root>
<VolumePopover />
</div>
</Tooltip.Provider>
</div>
{/* Hotkeys */}
<Hotkey keys="Space" action="togglePaused" />
<Hotkey keys="k" action="togglePaused" />
<Hotkey keys="m" action="toggleMuted" />
<Hotkey keys="ArrowRight" action="seekStep" value={5} />
<Hotkey keys="ArrowLeft" action="seekStep" value={-5} />
<Hotkey keys="l" action="seekStep" value={10} />
<Hotkey keys="j" action="seekStep" value={-10} />
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
<Hotkey keys="0-9" action="seekToPercent" />
<Hotkey keys="Home" action="seekToPercent" value={0} />
<Hotkey keys="End" action="seekToPercent" value={100} />
<Hotkey keys=">" action="speedUp" />
<Hotkey keys="<" action="speedDown" />
{/* Input Feedback */}
<StatusAnnouncer />
</Container>
</Player.Provider>
);
}
// ================================================================
// Components
// ================================================================
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
return (
<button
ref={ref}
type="button"
className={`media-button media-button--subtle media-button--icon ${className ?? ''}`}
{...props}
/>
);
});
function VolumePopover(): ReactNode {
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
const muteButton = (
<MuteButton className="media-button--mute" render={<Button />}>
<VolumeOffIcon className="media-icon media-icon--volume-off" />
<VolumeLowIcon className="media-icon media-icon--volume-low" />
<VolumeHighIcon className="media-icon media-icon--volume-high" />
</MuteButton>
);
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="left" boundary="viewport">
<Popover.Trigger render={muteButton} />
<Popover.Popup className="media-popover media-popover--volume">
<VolumeSlider.Root className="media-slider" orientation="horizontal" thumbAlignment="edge">
<VolumeSlider.Track className="media-slider__track">
<VolumeSlider.Fill className="media-slider__fill" />
</VolumeSlider.Track>
<VolumeSlider.Thumb className="media-slider__thumb media-slider__thumb--persistent" />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Root>
);
}
/* ==========================================================================
Reset
========================================================================== */
.media-minimal-skin *,
.media-minimal-skin *::before,
.media-minimal-skin *::after {
box-sizing: border-box;
}
.media-minimal-skin img,
.media-minimal-skin video,
.media-minimal-skin svg {
display: block;
max-width: 100%;
}
.media-minimal-skin button {
font: inherit;
}
.media-minimal-skin [hidden][hidden] {
/* Keep authored templates hidden even when component classes set display. */
display: none;
}
@media (prefers-reduced-motion: no-preference) {
.media-minimal-skin {
interpolate-size: allow-keywords;
}
}
/* ==========================================================================
Root Container
========================================================================== */
.media-minimal-skin {
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
--media-icon-size: 18px;
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
position: relative;
display: block;
width: 100%;
height: 100%;
container: media-root / inline-size;
font-family:
Inter Variable,
Inter,
ui-sans-serif,
system-ui,
sans-serif;
font-size: 0.8125rem; /* 13px at 100% font size */
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
scrollbar-width: thin;
border-radius: var(--media-border-radius, 0.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-color-scrollbar-thumb);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
/* ==========================================================================
Buffering Indicator
========================================================================== */
.media-minimal-skin .media-buffering-indicator {
position: absolute;
inset: 0;
z-index: 10;
display: none;
place-content: center;
color: oklch(1 0 0);
pointer-events: none;
&:not([data-visible]) {
--media-spinner-animation: none;
}
&[data-visible] {
display: grid;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-minimal-skin .media-error:not([data-open]) {
display: none;
}
.media-minimal-skin .media-error__title {
font-weight: 600;
line-height: 1.25;
}
.media-minimal-skin .media-error__description {
overflow-wrap: anywhere;
opacity: 0.7;
}
.media-minimal-skin .media-error__actions {
display: flex;
gap: 0.5rem;
& > * {
flex: 1;
}
}
.media-minimal-skin .media-error[data-open] ~ .media-controls * {
visibility: hidden;
}
/* ==========================================================================
Controls
========================================================================== */
.media-minimal-skin .media-controls {
display: flex;
align-items: center;
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
}
/* ==========================================================================
Time Controls & Display
========================================================================== */
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: 0.75rem;
align-items: center;
container: media-time-controls / inline-size;
}
.media-minimal-skin .media-time-group {
display: flex;
gap: 0.25rem;
align-items: center;
}
.media-minimal-skin .media-time {
font-variant-numeric: tabular-nums;
}
.media-minimal-skin .media-time[role="button"] {
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: 0.25rem;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: 1rem;
corner-shape: squircle;
}
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
.media-minimal-skin .media-time--current,
.media-minimal-skin .media-time-separator {
display: none;
}
@container media-root (width > 42rem) {
.media-minimal-skin .media-time-controls {
flex-direction: row;
}
.media-minimal-skin .media-time--duration,
.media-minimal-skin .media-time-separator {
color: oklch(from currentColor l c h / 0.6);
}
.media-minimal-skin .media-time--current,
.media-minimal-skin .media-time-separator {
display: inline;
}
}
/* ==========================================================================
Buttons
========================================================================== */
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
min-height: 0;
padding: 0.5rem 1rem;
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: 0.5rem;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:active {
scale: 0.98;
}
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: 1rem;
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: oklch(0 0 0);
text-shadow: none;
background: oklch(1 0 0);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
background: oklch(from currentColor l c h / 0.1);
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
width: 2.375rem;
aspect-ratio: 1;
padding: 0;
&:active {
scale: 0.9;
}
& .media-icon__container {
display: grid;
}
& .media-icon {
grid-area: 1 / 1;
transition-behavior: allow-discrete;
transition-property: display, opacity;
transition-duration: 150ms;
transition-timing-function: ease-out;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
& .media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 10px; /* Hard coded due to size limitations. */
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
}
&:has(.media-icon--flipped) .media-icon__label {
right: unset;
left: -1px;
}
}
/* Playback rate button */
.media-minimal-skin .media-button--playback-rate {
padding: 0;
font-variant-numeric: tabular-nums;
&::after {
width: 4ch;
content: attr(data-rate) "\00D7";
}
&[data-inline-rate-label]::after {
content: none;
}
}
/* Settings button */
.media-minimal-skin .media-button--settings {
display: none;
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
&[aria-expanded="true"] .media-icon--settings {
transform: rotate(90deg);
}
}
.media-minimal-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
display: grid;
}
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
text content. */
.media-minimal-skin .media-button--live {
display: inline-flex;
gap: 0.4rem;
align-items: center;
width: auto;
aspect-ratio: auto;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
transition: background-color 150ms ease-out;
}
&[data-live-edge]::before {
background-color: oklch(0.65 0.22 27);
}
}
/* ==========================================================================
Button Groups
========================================================================== */
.media-minimal-skin .media-button-group {
display: flex;
gap: 0.075rem;
align-items: center;
@container media-root (width > 42rem) {
gap: 0.125rem;
}
}
/* ==========================================================================
Badge
========================================================================== */
.media-minimal-skin .media-badge {
padding: 0.25rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
border-radius: 0.25rem;
}
/* ==========================================================================
Icons
========================================================================== */
.media-minimal-skin .media-icon__container {
position: relative;
}
.media-minimal-skin .media-icon {
flex-shrink: 0;
width: var(--media-icon-size);
height: var(--media-icon-size);
}
.media-minimal-skin .media-icon--flipped {
scale: -1 1;
}
/* ==========================================================================
Slider
========================================================================== */
.media-minimal-skin .media-slider {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: calc(infinity * 1px);
&[data-orientation="horizontal"] {
width: 100%;
min-width: 5rem;
height: 2rem;
}
&[data-orientation="vertical"] {
width: 2rem;
height: 4.5rem;
}
}
/* Track */
.media-minimal-skin .media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: inherit;
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: 0.1875rem;
}
&[data-orientation="vertical"] {
width: 0.1875rem;
height: 100%;
}
}
/* Thumb */
.media-minimal-skin .media-slider__thumb {
position: absolute;
z-index: 10;
width: 0.75rem;
height: 0.75rem;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
background-color: currentColor;
border-radius: calc(infinity * 1px);
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
0 1px 3px 0 oklch(0 0 0 / 0.15),
0 1px 2px -1px oklch(0 0 0 / 0.15);
opacity: 0;
transform-origin: center;
scale: 0.7;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale, outline-offset;
&[data-orientation="horizontal"] {
top: 50%;
left: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-fill));
left: 50%;
}
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
.media-minimal-skin .media-slider:hover .media-slider__thumb,
.media-minimal-skin .media-slider:focus-within .media-slider__thumb,
.media-minimal-skin .media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
/* Preview */
.media-minimal-skin .media-slider__preview {
& .media-slider__value,
&::before {
opacity: 0;
scale: 0.5;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
& .media-slider__value {
position: absolute;
bottom: 1.5rem;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
filter: blur(8px);
translate: -50% 0.5rem;
transition-property: filter, opacity, scale, translate;
}
&::before {
display: block;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
}
&[data-pointing] .media-slider__value,
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
&[data-pointing] .media-slider__value {
filter: blur(0);
translate: -50% 0;
}
&[data-orientation="horizontal"]::before {
min-width: 1px;
height: 1.25rem;
}
&[data-orientation="vertical"]::before {
width: 1.25rem;
min-height: 1px;
}
}
/* Shared track fills */
.media-minimal-skin .media-slider__buffer,
.media-minimal-skin .media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
}
.media-minimal-skin .media-slider__buffer[data-orientation="horizontal"],
.media-minimal-skin .media-slider__fill[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
}
.media-minimal-skin .media-slider__buffer[data-orientation="vertical"],
.media-minimal-skin .media-slider__fill[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
}
/* Buffer */
.media-minimal-skin .media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
transition-timing-function: ease-out;
transition-duration: 0.25s;
&[data-orientation="horizontal"] {
width: var(--media-slider-buffer);
transition-property: width;
}
&[data-orientation="vertical"] {
height: var(--media-slider-buffer);
transition-property: height;
}
}
/* Fill */
.media-minimal-skin .media-slider__fill {
background-color: currentColor;
&[data-orientation="horizontal"] {
width: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
height: var(--media-slider-fill);
}
}
/* Dragging — thumb and fill follow the pointer position */
.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
width: var(--media-slider-pointer);
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
height: var(--media-slider-pointer);
}
/* ==========================================================================
Popups & Animations
========================================================================== */
.media-minimal-skin {
--media-popup-transition:
opacity var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
filter var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
transform var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
scale var(--media-popup-transition-timing-function) var(--media-popup-transition-duration);
}
.media-minimal-skin .media-popover,
.media-minimal-skin .media-tooltip {
--popup-translate-distance: 0.5rem;
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
filter: blur(4px);
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--popup-translate-x-distance, 0), var(--popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--popup-translate-y-distance: var(--popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--popup-translate-y-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--popup-translate-x-distance: var(--popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--popup-translate-x-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: left;
}
/* Safe area between trigger and popup */
&::before {
position: absolute;
pointer-events: inherit;
content: "";
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
inset-inline: 0;
width: 100%;
}
&[data-side="top"]::before {
top: 100%;
}
&[data-side="bottom"]::before {
bottom: 100%;
}
&[data-side="left"]::before,
&[data-side="right"]::before {
inset-block: 0;
height: 100%;
}
&[data-side="left"]::before {
left: 100%;
}
&[data-side="right"]::before {
right: 100%;
}
}
.media-minimal-skin .media-popover {
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-popover-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-popover-side-offset);
}
}
.media-minimal-skin .media-tooltip {
padding: 0.25rem 0.5rem;
font-size: 0.75rem; /* 12px at 100% font size */
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: 0.5rem;
box-shadow:
0 0 0 1px var(--media-tooltip-border-color),
0 4px 6px -1px oklch(0 0 0 / 0.2),
0 2px 4px -2px oklch(0 0 0 / 0.2);
backdrop-filter: var(--media-tooltip-backdrop-filter);
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
column-gap: 0.25rem;
align-items: center;
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-tooltip-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-tooltip-side-offset);
}
& .media-tooltip__kbd {
min-width: 1.5em;
padding: 0.1em;
margin-right: -0.25rem;
font-family: inherit;
font-size: 90%;
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: 0.25rem;
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
/* ==========================================================================
Menus
Note: Menus use `.media-popover` styles for positioning and transitions.
========================================================================== */
.media-minimal-skin .media-menu {
--menu-transition-duration: 250ms;
--menu-max-height: 14rem;
--menu-padding: 0.5rem;
--menu-border-radius: 1rem;
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--menu-border-radius);
box-shadow:
0 0 0 1px var(--media-popover-border-color),
0 4px 6px -1px oklch(0 0 0 / 0.1),
0 2px 4px -2px oklch(0 0 0 / 0.1);
backdrop-filter: var(--media-popover-backdrop-filter);
@media (prefers-reduced-motion: reduce) {
--menu-transition-duration: 0ms;
}
& .media-menu__panel {
position: absolute;
inset: 0;
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-in-out;
transition-duration: var(--menu-transition-duration);
transition-property: translate, filter;
will-change: translate, filter;
&[data-starting-style],
&[data-ending-style] {
overflow: hidden;
}
/* Root settings view — slides out when a submenu is active */
&[data-menu-root-view][data-menu-view-state="inactive"] {
filter: blur(8px);
translate: -100% 0;
/* Hide the indicator while transitioning */
& .media-menu__group::before {
display: none;
}
}
/* Submenu panels — slide in/out alongside the root view */
&[data-submenu] {
z-index: 10;
&:not([data-open], [data-ending-style]) {
translate: -100% 0;
transition-property: none;
}
&[data-starting-style],
&[data-ending-style] {
pointer-events: none;
filter: blur(8px);
}
&[data-starting-style][data-direction="forward"],
&[data-ending-style][data-direction="back"] {
translate: 100% 0;
}
&[data-ending-style][data-direction="forward"],
&[data-starting-style][data-direction="back"] {
translate: -100% 0;
}
}
}
& .media-menu__separator {
margin: 0.25rem 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
& .media-menu__group {
display: flex;
flex-direction: column;
gap: 0.125rem;
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --media-menu-item-highlight-anchor;
inset: anchor(inside);
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.1);
border-radius: var(--menu-item-border-radius);
transition: inset ease-in-out 100ms;
}
}
}
& .media-menu__item,
& .media-menu__back {
display: flex;
gap: 0.375rem;
align-items: center;
padding: 0.375rem 0.5rem;
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-out;
transition-duration: 100ms;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
@supports (top: anchor(top)) {
anchor-name: --media-menu-item-highlight-anchor;
background-color: transparent;
}
}
& .media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__indicator {
flex-shrink: 0;
margin-right: -0.25rem;
margin-left: auto;
opacity: 0;
}
& .media-menu__item {
justify-content: space-between;
font-variant-numeric: tabular-nums;
color: inherit;
&[aria-disabled="true"] {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
&[aria-checked="true"] .media-menu__indicator {
opacity: 1;
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
& .media-menu__tier {
padding-top: 1px;
padding-left: 0.125rem;
font-size: 0.5625rem;
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
& .media-menu__back {
width: 100%;
margin-bottom: 0.125rem;
}
& .media-menu__hint {
display: inline-flex;
gap: 0.25rem;
align-items: center;
min-width: 0;
padding-left: 0.5rem;
margin-left: auto;
font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
max-width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
width: 0.875rem;
height: 0.875rem;
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: 12rem;
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
transition:
var(--media-popup-transition),
height var(--media-popup-transition-timing-function) var(--menu-transition-duration),
width var(--media-popup-transition-timing-function) var(--menu-transition-duration);
/* Don't transition width and height on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
/* ==========================================================================
Icon State Visibility for Audio Skins
Data-attribute-driven visibility rules for multi-state icon buttons.
Uses :is() with both element selectors (for HTML custom element wrappers)
and class selectors (for React rendered SVG elements).
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon--restart,
.media-button--play .media-icon--play,
.media-button--play .media-icon--pause,
.media-button--mute .media-icon--volume-off,
.media-button--mute .media-icon--volume-low,
.media-button--mute .media-icon--volume-high {
display: none;
opacity: 0;
}
/* --- Active icon per state --- */
/* Play: ended → restart */
.media-button--play[data-ended] .media-icon--restart,
/* Play: paused or not yet started (not ended) → play */
.media-button--play:not([data-ended])[data-paused] .media-icon--play,
.media-button--play:not([data-ended]):not([data-started]) .media-icon--play,
/* Play: started and not paused/ended → pause */
.media-button--play[data-started]:not([data-paused]):not([data-ended]) .media-icon--pause,
/* Mute: muted → volume off */
.media-button--mute[data-muted] .media-icon--volume-off,
/* Mute: volume low (not muted) → volume low */
.media-button--mute:not([data-muted])[data-volume-level="low"] .media-icon--volume-low,
/* Mute: volume high (not muted, not low) → volume high */
.media-button--mute:not([data-muted]):not([data-volume-level="low"]) .media-icon--volume-high {
display: block;
opacity: 1;
}
/* ==========================================================================
Root
========================================================================== */
.media-minimal-skin--audio {
--media-controls-background-color: oklch(1 0 0);
--media-controls-backdrop-filter: blur(16px) saturate(1.5);
--media-controls-border-color: oklch(0 0 0 / 0.1);
--media-controls-text-color: var(--media-color-primary, oklch(0 0 0));
--media-error-dialog-transition-duration: 250ms;
--media-error-dialog-transition-delay: 100ms;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
--media-tooltip-background-color: oklch(1 0 0);
--media-tooltip-border-color: oklch(0 0 0 / 0.1);
--media-tooltip-backdrop-filter: blur(16px) saturate(1.5);
--media-tooltip-text-color: currentColor;
--media-tooltip-side-offset: 0.75rem;
--media-tooltip-boundary-offset: 0.75rem;
--media-popover-background-color: var(--media-tooltip-background-color);
--media-popover-border-color: var(--media-tooltip-border-color);
--media-popover-backdrop-filter: var(--media-tooltip-backdrop-filter);
--media-popover-side-offset: var(--media-tooltip-side-offset);
--media-popover-boundary-offset: var(--media-tooltip-boundary-offset);
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-color-scheme: dark) {
--media-controls-background-color: oklch(0 0 0);
--media-controls-border-color: oklch(1 0 0 / 0.1);
--media-controls-text-color: var(--media-color-primary, oklch(1 0 0));
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-tooltip-background-color: oklch(1 0 0);
}
@media (prefers-color-scheme: dark) and ((prefers-reduced-transparency: reduce) or (prefers-contrast: more)) {
--media-tooltip-background-color: oklch(0 0 0);
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-minimal-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: 1rem;
align-items: center;
padding-inline: 1.25rem 0.5rem;
background-color: oklch(from var(--media-controls-background-color) l c h / 1);
border-radius: calc(infinity * 1px);
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: ease-out;
transition-duration: var(--media-error-dialog-transition-duration);
transition-property: opacity, filter, scale;
}
.media-minimal-skin--audio .media-error[data-starting-style] .media-error__dialog,
.media-minimal-skin--audio .media-error[data-ending-style] .media-error__dialog {
opacity: 0;
filter: blur(4px);
scale: 0.95;
}
.media-minimal-skin--audio .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
.media-minimal-skin--audio .media-error__content {
display: flex;
flex: 1;
gap: 0.5rem;
align-items: center;
}
/* ==========================================================================
Controls
========================================================================== */
.media-minimal-skin--audio .media-controls {
gap: 0.5rem;
padding: 0.375rem;
color: var(--media-controls-text-color);
border-radius: var(--media-border-radius, 1rem);
box-shadow: 0 0 0 1px var(--media-controls-border-color);
}
/* ==========================================================================
Buffering (spinner over play button — overlay only, no layout change)
========================================================================== */
.media-minimal-skin--audio .media-button--play__wrapper {
position: relative;
display: inline-flex;
}
.media-minimal-skin--audio .media-button--play__wrapper .media-buffering-indicator {
color: inherit;
}
.media-minimal-skin--audio
.media-button--play__wrapper:has(.media-buffering-indicator[data-visible])
.media-button--play
.media-icon {
opacity: 0;
}
/* ==========================================================================
Popups & Animations
========================================================================== */
.media-minimal-skin--audio .media-popover--volume {
padding: 0 0 0 4rem;
background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%);
}
.media-minimal-skin--audio .media-slider__preview .media-slider__value {
bottom: 2.5rem;
}
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { createPlayer, Container, usePlayer, BufferingIndicator, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster, type RenderProp } from '@videojs/react';
import { Audio, audioFeatures } from '@videojs/react/audio';
import './player.css';
const button = {
base: "flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden",
primary: "bg-white text-black font-medium text-shadow-none",
subtle: "bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10",
icon: "grid w-[2.375rem] aspect-square p-0 active:scale-90",
live: "inline-flex items-center gap-1.5 aspect-auto w-auto px-3 py-2 text-xs font-semibold uppercase tracking-wider leading-none before:inline-block before:size-2 before:shrink-0 before:rounded-full before:bg-current/40 before:transition-colors before:duration-150 before:ease-out before:content-[\"\"] data-[live-edge]:before:bg-red-500",
};
const buttonGroup = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5";
const controls = "peer/controls @container/media-controls flex items-center bg-(--media-controls-background-color) [backdrop-filter:var(--media-controls-backdrop-filter)] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) p-1.5 gap-2 rounded-(--media-border-radius,1rem) peer-data-open/error:**:invisible text-(--media-controls-text-color) ring-1 ring-(color:--media-controls-border-color)";
const error = {
root: "peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none",
dialog: "absolute inset-0 z-20 flex items-center gap-4 rounded-full px-5 pr-2 bg-(--media-controls-background-color) transition-[opacity,filter,scale] ease-out duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-xs group-data-starting-style/error:scale-95 group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-xs group-data-ending-style/error:scale-95 group-data-ending-style/error:delay-0",
content: "flex flex-1 items-center gap-2",
title: "font-semibold leading-tight",
description: "opacity-70 wrap-anywhere",
actions: "flex gap-2 *:flex-1",
};
const icon = "block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out";
const iconContainer = "relative grid";
const iconFlipped = "[scale:-1_1]";
const iconState = {
play: {
button: "group",
restart: "hidden opacity-0 group-data-ended:block group-data-ended:opacity-100",
play: "hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100",
pause: "hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100",
},
mute: {
button: "group",
volumeOff: "hidden opacity-0 group-data-muted:block group-data-muted:opacity-100",
volumeLow: "hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100",
volumeHigh: "hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100",
},
fullscreen: {
button: "group",
enter: "hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100",
exit: "hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100",
},
captions: {
button: "group",
off: "hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100",
on: "hidden opacity-0 group-data-active:block group-data-active:opacity-100",
},
pip: {
button: "group",
off: "hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100",
on: "hidden opacity-0 group-data-pip:block group-data-pip:opacity-100",
},
cast: {
button: "group",
enter: "hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100",
exit: "hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100",
},
airplay: {
button: "group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]",
enter: "hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100",
exit: "hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100",
},
};
const menu = {
root: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.5rem] [--menu-border-radius:1rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none overflow-auto!",
settings: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.5rem] [--menu-border-radius:1rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none [--media-popup-transition:var(--media-popup-base-transition),height_var(--media-popup-transition-timing-function)_var(--menu-transition-duration),width_var(--media-popup-transition-timing-function)_var(--menu-transition-duration)] data-starting-style:[--media-popup-transition:var(--media-popup-base-transition)] data-ending-style:[--media-popup-transition:var(--media-popup-base-transition)] min-w-48 w-(--media-menu-width) h-(--media-menu-height) !overflow-hidden",
group: "flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden",
item: "flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50",
separator: "my-1 border-b border-[oklch(1_0_0/0.1)]",
tier: "pl-0.5 pt-px text-[0.5625rem] font-semibold leading-none text-current/70",
indicator: "ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100",
icon: "shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]",
rootView: "absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] group/menu-root-view data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur",
submenuPanel: "absolute inset-0 overflow-auto overscroll-none p-(--menu-padding) outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate,filter] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full",
back: "flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full",
hint: "ml-auto inline-flex min-w-0 items-center gap-1 pl-2 text-xs text-current/65",
hintLabel: "max-w-24 overflow-hidden text-ellipsis whitespace-nowrap",
chevron: "size-3.5",
settingsGroup: "group/settings",
settingsTrigger: "group hidden group-has-[[data-availability=available]]/settings:grid",
settingsIcon: "transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0",
};
const playButton = {
wrapper: "group/play inline-flex relative",
control: "peer-data-visible/play-buffering:[&>svg]:opacity-0",
bufferingRoot: "peer/play-buffering absolute inset-0 z-10 hidden place-content-center pointer-events-none text-inherit not-data-visible:[--media-spinner-animation:none] data-visible:grid",
};
const playbackRate = {
button: "tabular-nums after:w-[4ch] after:content-[attr(data-rate)_'×'] data-[inline-rate-label]:after:content-none",
};
const popup = {
base: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full",
popover: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset)",
tooltip: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)",
tooltipShortcut: "min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded",
volume: "[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) p-0 pl-16 bg-transparent bg-gradient-to-l from-(--media-controls-background-color) from-80% to-transparent",
};
const root = "**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,0.75rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 scrollbar-thin scrollbar-thumb-current/30 [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-auto [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-thumb-current/80 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] [--media-controls-background-color:oklch(1_0_0)] [--media-controls-backdrop-filter:blur(16px)_saturate(1.5)] [--media-controls-border-color:oklch(0_0_0/0.1)] [--media-controls-text-color:var(--media-color-primary,oklch(0_0_0))] [--media-error-dialog-transition-duration:250ms] [--media-error-dialog-transition-delay:100ms] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-tooltip-background-color:oklch(1_0_0)] [--media-tooltip-border-color:oklch(0_0_0/0.1)] [--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)] [--media-tooltip-text-color:currentColor] [--media-tooltip-side-offset:0.75rem] [--media-tooltip-boundary-offset:0.75rem] [--media-popover-background-color:var(--media-tooltip-background-color)] [--media-popover-border-color:var(--media-tooltip-border-color)] [--media-popover-backdrop-filter:var(--media-tooltip-backdrop-filter)] [--media-popover-side-offset:var(--media-tooltip-side-offset)] [--media-popover-boundary-offset:var(--media-tooltip-boundary-offset)] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-popup-transition-duration:0ms] dark:[--media-controls-background-color:oklch(0_0_0)] dark:[--media-controls-border-color:oklch(1_0_0/0.1)] dark:[--media-controls-text-color:var(--media-color-primary,oklch(1_0_0))] [@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(1_0_0)] contrast-more:[--media-tooltip-background-color:oklch(1_0_0)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(0_0_0)] dark:contrast-more:[--media-tooltip-background-color:oklch(0_0_0)]";
const seek = {
label: "text-[10px] font-medium tracking-tighter tabular-nums",
labelForward: "absolute -right-px -bottom-0.75",
labelBackward: "absolute -left-px -bottom-0.75",
};
const slider = {
root: "group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]",
track: "relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-0.75 data-[orientation=vertical]:w-0.75 data-[orientation=vertical]:h-full",
fill: {
base: "absolute rounded-[inherit] pointer-events-none",
fill: "bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)",
buffer: "bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer,0) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)",
},
thumb: {
base: "z-10 absolute size-3 -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,scale,outline-offset] duration-150 ease-out select-none outline-2 outline-transparent -outline-offset-2 focus-visible:outline-current focus-visible:outline-offset-2 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill,0))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))]",
interactive: "opacity-0 scale-70 origin-center group-hover/slider:opacity-100 group-hover/slider:scale-100 group-focus-within/slider:opacity-100 group-focus-within/slider:scale-100",
},
preview: "group/preview before:block before:bg-current/35 before:opacity-0 before:scale-50 before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 data-[orientation=horizontal]:before:min-w-px data-[orientation=horizontal]:before:h-5 data-[orientation=vertical]:before:w-5 data-[orientation=vertical]:before:min-h-px peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden",
value: "absolute bottom-6 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none bottom-10",
};
const time = {
group: "flex items-center gap-1",
current: "hidden tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out supports-[corner-shape:squircle]:rounded-4 supports-[corner-shape:squircle]:[corner-shape:squircle] focus-visible:outline-current focus-visible:outline-offset-2 @2xl/media-root:inline",
separator: "hidden @2xl/media-root:inline @2xl/media-root:text-current/60",
duration: "tabular-nums @2xl/media-root:text-current/60",
controls: "@container flex flex-row-reverse items-center flex-1 gap-3 @2xl/media-root:flex-row",
};
interface MinimalAudioSkinProps {
src: string;
style?: CSSProperties;
className?: string;
}
function PlaybackRateRadioGroup(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
const { options, setValue, value } = state;
return (
<Menu.RadioGroup className={menu.group} value={value} onValueChange={setValue} aria-label="Playback rate">
{options.map((option) => (
<Menu.RadioItem key={option.value} className={menu.item} value={option.value} disabled={option.disabled}>
<span>{option.label}</span>
<Menu.ItemIndicator checked={option.value === value} forceMount className={menu.indicator}>
<CheckIcon className={`${icon} ${menu.icon}`} />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
);
}
function PlaybackRateTrigger(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
return (
<Menu.Trigger
disabled={state.disabled}
render={<PlaybackRateButton className={playbackRate.button} render={<Button />} />}
/>
);
}
export function MinimalAudioSkinTailwind({ children, className, ...rest }: MinimalAudioSkinProps): ReactNode {
return (
<Container className={`${root} ${className ?? ''}`} {...rest}>
{children}
<ErrorDialog.Root>
<ErrorDialog.Popup className={error.root}>
<div className={error.dialog}>
<div className={error.content}>
<ErrorDialog.Title className={error.title}>Something went wrong.</ErrorDialog.Title>
<ErrorDialog.Description className={error.description} />
</div>
<div className={error.actions}>
<ErrorDialog.Close className={`${button.base} ${button.subtle}`}>OK</ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<div className={controls}>
<Tooltip.Provider>
<div className={buttonGroup}>
<span className={playButton.wrapper}>
<BufferingIndicator
render={(props) => (
<div {...props} className={`${playButton.bufferingRoot} ${props.className}`}>
<SpinnerIcon className={icon} />
</div>
)}
/>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<PlayButton className={`${iconState.play.button} ${playButton.control}`} render={<Button />}>
<RestartIcon className={`${icon} ${iconState.play.restart}`} />
<PlayIcon className={`${icon} ${iconState.play.play}`} />
<PauseIcon className={`${icon} ${iconState.play.pause}`} />
</PlayButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
</span>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={-SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={`${icon} ${iconFlipped}`} />
<span className={`${seek.label} ${seek.labelBackward}`}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<SeekButton seconds={SEEK_TIME} render={<Button />}>
<span className={iconContainer}>
<SeekIcon className={icon} />
<span className={`${seek.label} ${seek.labelForward}`}>{SEEK_TIME}</span>
</span>
</SeekButton>
}
/>
<Tooltip.Popup className={`${popup.tooltip}`}>
<Tooltip.Label />
<Tooltip.Shortcut className={popup.tooltipShortcut} />
</Tooltip.Popup>
</Tooltip.Root>
</div>
<div className={time.controls}>
<Time.Group className={time.group}>
<Time.Value toggle type="current" className={time.current} />
<Time.Separator className={time.separator} />
<Time.Value type="duration" className={time.duration} />
</Time.Group>
<TimeSlider.Root render={<SliderRoot />}>
<TimeSlider.Track render={<SliderTrack />}>
<TimeSlider.Fill render={<SliderFill />} />
<TimeSlider.Buffer render={<SliderBuffer />} />
</TimeSlider.Track>
<TimeSlider.Thumb render={<SliderThumb />} />
<TimeSlider.Preview className={slider.preview}>
<TimeSlider.Value type="pointer" className={slider.value} />
</TimeSlider.Preview>
</TimeSlider.Root>
</div>
<div className={buttonGroup}>
<Menu.Root side="top" align="center" boundary="viewport">
<PlaybackRateTrigger />
<Menu.Content className={`${popup.popover} ${menu.root}`}>
<PlaybackRateRadioGroup />
</Menu.Content>
</Menu.Root>
<VolumePopover />
</div>
</Tooltip.Provider>
</div>
</Container>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
// ================================================================
// Components
// ================================================================
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
return (
<button ref={ref} type="button" className={`${button.base} ${button.subtle} ${button.icon} ${className ?? ''}`} {...props} />
);
});
const SliderRoot = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderRoot({ className, ...props }, ref) {
return <div ref={ref} className={`${slider.root} ${className ?? ''}`} {...props} />;
});
const SliderTrack = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderTrack(
{ className, ...props },
ref
) {
return <div ref={ref} className={`${slider.track} ${className ?? ''}`} {...props} />;
});
const SliderFill = forwardRef<HTMLDivElement, ComponentProps<'div'> & { type?: 'fill' | 'buffer' }>(function SliderFill(
{ type = 'fill', className, ...props },
ref
) {
return (
<div
ref={ref}
className={`${slider.fill.base} ${type === 'fill' ? slider.fill.fill : slider.fill.buffer} ${className ?? ''}`}
{...props}
/>
);
});
const SliderBuffer = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderBuffer(props, ref) {
return <SliderFill type="buffer" ref={ref} {...props} />;
});
const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persistent?: boolean }>(function SliderThumb(
{ persistent, className, ...props },
ref
) {
return (
<div
ref={ref}
className={`${slider.thumb.base} ${persistent ? undefined : slider.thumb.interactive} ${className ?? ''}`}
{...props}
/>
);
});
function VolumePopover(): ReactNode {
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
const muteButton = (
<MuteButton className={iconState.mute.button} render={<Button />}>
<VolumeOffIcon className={`${icon} ${iconState.mute.volumeOff}`} />
<VolumeLowIcon className={`${icon} ${iconState.mute.volumeLow}`} />
<VolumeHighIcon className={`${icon} ${iconState.mute.volumeHigh}`} />
</MuteButton>
);
if (volumeUnsupported) return muteButton;
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="left" boundary="viewport">
<Popover.Trigger render={muteButton} />
<Popover.Popup className={`${popup.volume}`}>
<VolumeSlider.Root orientation="horizontal" thumbAlignment="edge" render={<SliderRoot />}>
<VolumeSlider.Track render={<SliderTrack />}>
<VolumeSlider.Fill render={<SliderFill />} />
</VolumeSlider.Track>
<VolumeSlider.Thumb render={(props) => <SliderThumb persistent {...props} />} />
</VolumeSlider.Root>
</Popover.Popup>
</Popover.Root>
);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/audio-minimal-ui.js"></script>
<link rel="stylesheet" href="./player.css">
<audio-player>
<media-container class="media-minimal-skin media-minimal-skin--audio">
<audio src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"></audio>
<media-error-dialog class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-alert-dialog-title class="media-error__title">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="media-error__description"></media-alert-dialog-description>
</div>
<div class="media-error__actions">
<media-alert-dialog-close class="media-button media-button--subtle">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<div class="media-controls">
<media-tooltip-group>
<div class="media-button-group">
<span class="media-button--play__wrapper">
<media-buffering-indicator class="media-buffering-indicator">
<media-icon name="spinner" family="minimal" class="media-icon"></media-icon>
</media-buffering-indicator>
<media-play-button commandfor="play-tooltip" class="media-button media-button--subtle media-button--icon media-button--play">
<media-icon name="restart" family="minimal" class="media-icon media-icon--restart"></media-icon>
<media-icon name="play" family="minimal" class="media-icon media-icon--play"></media-icon>
<media-icon name="pause" family="minimal" class="media-icon media-icon--pause"></media-icon>
</media-play-button>
<media-tooltip id="play-tooltip" side="top" boundary="viewport" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
</span>
<media-seek-button commandfor="seek-backward-tooltip" seconds="-10" class="media-button media-button--subtle media-button--icon media-button--seek">
<span class="media-icon__container">
<media-icon name="seek" family="minimal" class="media-icon media-icon--seek media-icon--flipped"></media-icon>
<span class="media-icon__label">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-backward-tooltip" side="top" boundary="viewport" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-forward-tooltip" seconds="10" class="media-button media-button--subtle media-button--icon media-button--seek">
<span class="media-icon__container">
<media-icon name="seek" family="minimal" class="media-icon media-icon--seek"></media-icon>
<span class="media-icon__label">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-forward-tooltip" side="top" boundary="viewport" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
</div>
<div class="media-time-controls">
<media-time-group class="media-time-group">
<media-time toggle type="current" class="media-time media-time--current"></media-time>
<media-time-separator class="media-time-separator"></media-time-separator>
<media-time type="duration" class="media-time media-time--duration"></media-time>
</media-time-group>
<media-time-slider class="media-slider">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
<media-slider-buffer class="media-slider__buffer"></media-slider-buffer>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb"></media-slider-thumb>
<media-slider-preview class="media-slider__preview">
<media-slider-value type="pointer" class="media-slider__value media-time"></media-slider-value>
</media-slider-preview>
</media-time-slider>
</div>
<div class="media-button-group">
<media-playback-rate-button commandfor="playback-rate-menu" class="media-button media-button--subtle media-button--icon media-button--playback-rate"></media-playback-rate-button>
<media-menu id="playback-rate-menu" side="top" align="center" boundary="viewport" class="media-popover media-menu">
<media-playback-rate-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="media-menu__indicator">
<media-icon name="check" family="minimal" class="media-icon"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
</media-menu>
<media-mute-button commandfor="audio-volume-popover" class="media-button media-button--subtle media-button--icon media-button--mute">
<media-icon name="volume-off" family="minimal" class="media-icon media-icon--volume-off"></media-icon>
<media-icon name="volume-low" family="minimal" class="media-icon media-icon--volume-low"></media-icon>
<media-icon name="volume-high" family="minimal" class="media-icon media-icon--volume-high"></media-icon>
</media-mute-button>
<media-popover id="audio-volume-popover" open-on-hover delay="200" close-delay="100" side="left" boundary="viewport" class="media-popover media-popover--volume">
<media-volume-slider class="media-slider" orientation="horizontal" thumb-alignment="edge">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb media-slider__thumb--persistent"></media-slider-thumb>
</media-volume-slider>
</media-popover>
</div>
</media-tooltip-group>
</div>
</media-container>
</audio-player>
/* -------------------------------------------------------------------------- */
/* Global styles for the host document, outside of the Shadow DOM */
/* -------------------------------------------------------------------------- */
video-player,
live-video-player {
display: contents;
}
/*
Required to override any default video and image styles (such as
Tailwind's CSS reset) and ensure they fill the container as expected.
*/
video-player video,
video-player [slot="poster"],
live-video-player video,
live-video-player [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
video-player video::-webkit-media-text-track-container,
live-video-player video::-webkit-media-text-track-container {
z-index: 1;
font-family: inherit;
scale: 0.98;
translate: 0 var(--media-caption-track-y, 0);
transition: translate var(--media-caption-track-duration, 0) ease-out;
transition-delay: var(--media-caption-track-delay, 0);
}
/* -------------------------------------------------------------------------- */
/* Shared styles for all HTML skins */
/* -------------------------------------------------------------------------- */
media-tooltip-group {
display: contents;
}
:host {
/* `display:grid` fixes a weird issue with Safari when setting aspect-ratio */
display: grid;
width: 100%;
}
media-container {
min-width: 0;
min-height: 0;
}
/* Hide volume popover when volume control is unsupported (e.g., iOS Safari). */
.media-popover--volume:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
/* ==========================================================================
Reset
========================================================================== */
.media-minimal-skin *,
.media-minimal-skin *::before,
.media-minimal-skin *::after {
box-sizing: border-box;
}
.media-minimal-skin img,
.media-minimal-skin video,
.media-minimal-skin svg {
display: block;
max-width: 100%;
}
.media-minimal-skin button {
font: inherit;
}
.media-minimal-skin [hidden][hidden] {
/* Keep authored templates hidden even when component classes set display. */
display: none;
}
@media (prefers-reduced-motion: no-preference) {
.media-minimal-skin {
interpolate-size: allow-keywords;
}
}
/* ==========================================================================
Root Container
========================================================================== */
.media-minimal-skin {
--media-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-current-shadow-color-subtle: oklch(from var(--media-current-shadow-color) l c h / calc(alpha * 0.4));
--media-icon-size: 18px;
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.3);
position: relative;
display: block;
width: 100%;
height: 100%;
container: media-root / inline-size;
font-family:
Inter Variable,
Inter,
ui-sans-serif,
system-ui,
sans-serif;
font-size: 0.8125rem; /* 13px at 100% font size */
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
line-height: 1.5;
letter-spacing: normal;
outline: 2px solid transparent;
outline-offset: -4px;
scrollbar-color: var(--media-color-scrollbar-thumb) transparent;
scrollbar-width: thin;
border-radius: var(--media-border-radius, 0.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-color-scrollbar-thumb);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-color-scrollbar-thumb: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
/* ==========================================================================
Buffering Indicator
========================================================================== */
.media-minimal-skin .media-buffering-indicator {
position: absolute;
inset: 0;
z-index: 10;
display: none;
place-content: center;
color: oklch(1 0 0);
pointer-events: none;
&:not([data-visible]) {
--media-spinner-animation: none;
}
&[data-visible] {
display: grid;
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-minimal-skin .media-error:not([data-open]) {
display: none;
}
.media-minimal-skin .media-error__title {
font-weight: 600;
line-height: 1.25;
}
.media-minimal-skin .media-error__description {
overflow-wrap: anywhere;
opacity: 0.7;
}
.media-minimal-skin .media-error__actions {
display: flex;
gap: 0.5rem;
& > * {
flex: 1;
}
}
.media-minimal-skin .media-error[data-open] ~ .media-controls * {
visibility: hidden;
}
/* ==========================================================================
Controls
========================================================================== */
.media-minimal-skin .media-controls {
display: flex;
align-items: center;
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
}
/* ==========================================================================
Time Controls & Display
========================================================================== */
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: 0.75rem;
align-items: center;
container: media-time-controls / inline-size;
}
.media-minimal-skin .media-time-group {
display: flex;
gap: 0.25rem;
align-items: center;
}
.media-minimal-skin .media-time {
font-variant-numeric: tabular-nums;
}
.media-minimal-skin .media-time[role="button"] {
cursor: pointer;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: 0.25rem;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: 1rem;
corner-shape: squircle;
}
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
.media-minimal-skin .media-time--current,
.media-minimal-skin .media-time-separator {
display: none;
}
@container media-root (width > 42rem) {
.media-minimal-skin .media-time-controls {
flex-direction: row;
}
.media-minimal-skin .media-time--duration,
.media-minimal-skin .media-time-separator {
color: oklch(from currentColor l c h / 0.6);
}
.media-minimal-skin .media-time--current,
.media-minimal-skin .media-time-separator {
display: inline;
}
}
/* ==========================================================================
Buttons
========================================================================== */
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
min-height: 0;
padding: 0.5rem 1rem;
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: 0.5rem;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: background-color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:active {
scale: 0.98;
}
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: 1rem;
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: oklch(0 0 0);
text-shadow: none;
background: oklch(1 0 0);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
background: oklch(from currentColor l c h / 0.1);
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
width: 2.375rem;
aspect-ratio: 1;
padding: 0;
&:active {
scale: 0.9;
}
& .media-icon__container {
display: grid;
}
& .media-icon {
grid-area: 1 / 1;
transition-behavior: allow-discrete;
transition-property: display, opacity;
transition-duration: 150ms;
transition-timing-function: ease-out;
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
& .media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 10px; /* Hard coded due to size limitations. */
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: -0.05em;
}
&:has(.media-icon--flipped) .media-icon__label {
right: unset;
left: -1px;
}
}
/* Playback rate button */
.media-minimal-skin .media-button--playback-rate {
padding: 0;
font-variant-numeric: tabular-nums;
&::after {
width: 4ch;
content: attr(data-rate) "\00D7";
}
&[data-inline-rate-label]::after {
content: none;
}
}
/* Settings button */
.media-minimal-skin .media-button--settings {
display: none;
& .media-icon--settings {
transition: transform 150ms ease-in-out;
@media (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
&[aria-expanded="true"] .media-icon--settings {
transform: rotate(90deg);
}
}
.media-minimal-skin .media-button-group:has([data-availability="available"]) .media-button--settings {
display: grid;
}
/* Live button — wide pill button with a status dot (gray → red at the live
edge) rendered via ::before, and "LIVE" text rendered as the button's own
text content. */
.media-minimal-skin .media-button--live {
display: inline-flex;
gap: 0.4rem;
align-items: center;
width: auto;
aspect-ratio: auto;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
content: "";
background-color: oklch(from currentColor l c h / 0.4);
border-radius: 50%;
transition: background-color 150ms ease-out;
}
&[data-live-edge]::before {
background-color: oklch(0.65 0.22 27);
}
}
/* ==========================================================================
Button Groups
========================================================================== */
.media-minimal-skin .media-button-group {
display: flex;
gap: 0.075rem;
align-items: center;
@container media-root (width > 42rem) {
gap: 0.125rem;
}
}
/* ==========================================================================
Badge
========================================================================== */
.media-minimal-skin .media-badge {
padding: 0.25rem 0.375rem;
font-size: 0.6875rem;
font-weight: 500;
line-height: 1;
color: oklch(from currentColor l c h / 0.85);
white-space: nowrap;
background-color: oklch(from currentColor l c h / 0.1);
border-radius: 0.25rem;
}
/* ==========================================================================
Icons
========================================================================== */
.media-minimal-skin .media-icon__container {
position: relative;
}
.media-minimal-skin .media-icon {
flex-shrink: 0;
width: var(--media-icon-size);
height: var(--media-icon-size);
}
.media-minimal-skin .media-icon--flipped {
scale: -1 1;
}
/* ==========================================================================
Slider
========================================================================== */
.media-minimal-skin .media-slider {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: calc(infinity * 1px);
&[data-orientation="horizontal"] {
width: 100%;
min-width: 5rem;
height: 2rem;
}
&[data-orientation="vertical"] {
width: 2rem;
height: 4.5rem;
}
}
/* Track */
.media-minimal-skin .media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: inherit;
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: 0.1875rem;
}
&[data-orientation="vertical"] {
width: 0.1875rem;
height: 100%;
}
}
/* Thumb */
.media-minimal-skin .media-slider__thumb {
position: absolute;
z-index: 10;
width: 0.75rem;
height: 0.75rem;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
background-color: currentColor;
border-radius: calc(infinity * 1px);
box-shadow:
0 0 0 1px var(--media-current-shadow-color, oklch(0 0 0 / 0.15)),
0 1px 3px 0 oklch(0 0 0 / 0.15),
0 1px 2px -1px oklch(0 0 0 / 0.15);
opacity: 0;
transform-origin: center;
scale: 0.7;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale, outline-offset;
&[data-orientation="horizontal"] {
top: 50%;
left: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-fill));
left: 50%;
}
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
}
.media-minimal-skin .media-slider:hover .media-slider__thumb,
.media-minimal-skin .media-slider:focus-within .media-slider__thumb,
.media-minimal-skin .media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
/* Preview */
.media-minimal-skin .media-slider__preview {
& .media-slider__value,
&::before {
opacity: 0;
scale: 0.5;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
& .media-slider__value {
position: absolute;
bottom: 1.5rem;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
filter: blur(8px);
translate: -50% 0.5rem;
transition-property: filter, opacity, scale, translate;
}
&::before {
display: block;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
}
&[data-pointing] .media-slider__value,
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
&[data-pointing] .media-slider__value {
filter: blur(0);
translate: -50% 0;
}
&[data-orientation="horizontal"]::before {
min-width: 1px;
height: 1.25rem;
}
&[data-orientation="vertical"]::before {
width: 1.25rem;
min-height: 1px;
}
}
/* Shared track fills */
.media-minimal-skin .media-slider__buffer,
.media-minimal-skin .media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
}
.media-minimal-skin .media-slider__buffer[data-orientation="horizontal"],
.media-minimal-skin .media-slider__fill[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
}
.media-minimal-skin .media-slider__buffer[data-orientation="vertical"],
.media-minimal-skin .media-slider__fill[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
}
/* Buffer */
.media-minimal-skin .media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
transition-timing-function: ease-out;
transition-duration: 0.25s;
&[data-orientation="horizontal"] {
width: var(--media-slider-buffer);
transition-property: width;
}
&[data-orientation="vertical"] {
height: var(--media-slider-buffer);
transition-property: height;
}
}
/* Fill */
.media-minimal-skin .media-slider__fill {
background-color: currentColor;
&[data-orientation="horizontal"] {
width: var(--media-slider-fill);
}
&[data-orientation="vertical"] {
height: var(--media-slider-fill);
}
}
/* Dragging — thumb and fill follow the pointer position */
.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__thumb[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="horizontal"] {
width: var(--media-slider-pointer);
}
.media-minimal-skin .media-slider[data-dragging] .media-slider__fill[data-orientation="vertical"] {
height: var(--media-slider-pointer);
}
/* ==========================================================================
Popups & Animations
========================================================================== */
.media-minimal-skin {
--media-popup-transition:
opacity var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
filter var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
transform var(--media-popup-transition-timing-function) var(--media-popup-transition-duration),
scale var(--media-popup-transition-timing-function) var(--media-popup-transition-duration);
}
.media-minimal-skin .media-popover,
.media-minimal-skin .media-tooltip {
--popup-translate-distance: 0.5rem;
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
filter: blur(4px);
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--popup-translate-x-distance, 0), var(--popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--popup-translate-y-distance: var(--popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--popup-translate-y-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--popup-translate-x-distance: var(--popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--popup-translate-x-distance: calc(var(--popup-translate-distance) * -1);
transform-origin: left;
}
/* Safe area between trigger and popup */
&::before {
position: absolute;
pointer-events: inherit;
content: "";
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
inset-inline: 0;
width: 100%;
}
&[data-side="top"]::before {
top: 100%;
}
&[data-side="bottom"]::before {
bottom: 100%;
}
&[data-side="left"]::before,
&[data-side="right"]::before {
inset-block: 0;
height: 100%;
}
&[data-side="left"]::before {
left: 100%;
}
&[data-side="right"]::before {
right: 100%;
}
}
.media-minimal-skin .media-popover {
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-popover-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-popover-side-offset);
}
}
.media-minimal-skin .media-tooltip {
padding: 0.25rem 0.5rem;
font-size: 0.75rem; /* 12px at 100% font size */
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: 0.5rem;
box-shadow:
0 0 0 1px var(--media-tooltip-border-color),
0 4px 6px -1px oklch(0 0 0 / 0.2),
0 2px 4px -2px oklch(0 0 0 / 0.2);
backdrop-filter: var(--media-tooltip-backdrop-filter);
/* `display: flex` must not apply while closed — it overrides UA `[popover]` hiding. */
&[data-open] {
display: flex;
column-gap: 0.25rem;
align-items: center;
}
&[data-side="top"]::before,
&[data-side="bottom"]::before {
height: var(--media-tooltip-side-offset);
}
&[data-side="left"]::before,
&[data-side="right"]::before {
width: var(--media-tooltip-side-offset);
}
& .media-tooltip__kbd {
min-width: 1.5em;
padding: 0.1em;
margin-right: -0.25rem;
font-family: inherit;
font-size: 90%;
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: 0.25rem;
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-availability="unsupported"]) {
display: none;
}
/* ==========================================================================
Menus
Note: Menus use `.media-popover` styles for positioning and transitions.
========================================================================== */
.media-minimal-skin .media-menu {
--menu-transition-duration: 250ms;
--menu-max-height: 14rem;
--menu-padding: 0.5rem;
--menu-border-radius: 1rem;
--menu-item-border-radius: calc(var(--menu-border-radius) - var(--menu-padding));
box-sizing: border-box;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--menu-max-height)), var(--menu-max-height));
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--menu-border-radius);
box-shadow:
0 0 0 1px var(--media-popover-border-color),
0 4px 6px -1px oklch(0 0 0 / 0.1),
0 2px 4px -2px oklch(0 0 0 / 0.1);
backdrop-filter: var(--media-popover-backdrop-filter);
@media (prefers-reduced-motion: reduce) {
--menu-transition-duration: 0ms;
}
& .media-menu__panel {
position: absolute;
inset: 0;
padding: var(--menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-in-out;
transition-duration: var(--menu-transition-duration);
transition-property: translate, filter;
will-change: translate, filter;
&[data-starting-style],
&[data-ending-style] {
overflow: hidden;
}
/* Root settings view — slides out when a submenu is active */
&[data-menu-root-view][data-menu-view-state="inactive"] {
filter: blur(8px);
translate: -100% 0;
/* Hide the indicator while transitioning */
& .media-menu__group::before {
display: none;
}
}
/* Submenu panels — slide in/out alongside the root view */
&[data-submenu] {
z-index: 10;
&:not([data-open], [data-ending-style]) {
translate: -100% 0;
transition-property: none;
}
&[data-starting-style],
&[data-ending-style] {
pointer-events: none;
filter: blur(8px);
}
&[data-starting-style][data-direction="forward"],
&[data-ending-style][data-direction="back"] {
translate: 100% 0;
}
&[data-ending-style][data-direction="forward"],
&[data-starting-style][data-direction="back"] {
translate: -100% 0;
}
}
}
& .media-menu__separator {
margin: 0.25rem 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
& .media-menu__group {
display: flex;
flex-direction: column;
gap: 0.125rem;
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --media-menu-item-highlight-anchor;
inset: anchor(inside);
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.1);
border-radius: var(--menu-item-border-radius);
transition: inset ease-in-out 100ms;
}
}
}
& .media-menu__item,
& .media-menu__back {
display: flex;
gap: 0.375rem;
align-items: center;
padding: 0.375rem 0.5rem;
text-align: left;
text-shadow: 0 1px 0 var(--media-current-shadow-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-out;
transition-duration: 100ms;
&:focus-visible {
outline-color: currentColor;
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
background-color: oklch(from currentColor l c h / 0.1);
@supports (top: anchor(top)) {
anchor-name: --media-menu-item-highlight-anchor;
background-color: transparent;
}
}
& .media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-current-shadow-color));
}
}
& .media-menu__indicator {
flex-shrink: 0;
margin-right: -0.25rem;
margin-left: auto;
opacity: 0;
}
& .media-menu__item {
justify-content: space-between;
font-variant-numeric: tabular-nums;
color: inherit;
&[aria-disabled="true"] {
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
&[aria-checked="true"] .media-menu__indicator {
opacity: 1;
}
&[data-availability="unavailable"],
&[data-availability="unsupported"] {
display: none;
}
}
& .media-menu__tier {
padding-top: 1px;
padding-left: 0.125rem;
font-size: 0.5625rem;
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
& .media-menu__back {
width: 100%;
margin-bottom: 0.125rem;
}
& .media-menu__hint {
display: inline-flex;
gap: 0.25rem;
align-items: center;
min-width: 0;
padding-left: 0.5rem;
margin-left: auto;
font-size: 0.75rem;
color: oklch(from currentColor l c h / 0.65);
}
& .media-menu__hint-label {
max-width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .media-menu__chevron {
width: 0.875rem;
height: 0.875rem;
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: 12rem;
height: var(--media-menu-height);
overflow: hidden;
/* Add height and width transitions. */
transition:
var(--media-popup-transition),
height var(--media-popup-transition-timing-function) var(--menu-transition-duration),
width var(--media-popup-transition-timing-function) var(--menu-transition-duration);
/* Don't transition width and height on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
/* ==========================================================================
Icon State Visibility for Audio Skins
Data-attribute-driven visibility rules for multi-state icon buttons.
Uses :is() with both element selectors (for HTML custom element wrappers)
and class selectors (for React rendered SVG elements).
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon--restart,
.media-button--play .media-icon--play,
.media-button--play .media-icon--pause,
.media-button--mute .media-icon--volume-off,
.media-button--mute .media-icon--volume-low,
.media-button--mute .media-icon--volume-high {
display: none;
opacity: 0;
}
/* --- Active icon per state --- */
/* Play: ended → restart */
.media-button--play[data-ended] .media-icon--restart,
/* Play: paused or not yet started (not ended) → play */
.media-button--play:not([data-ended])[data-paused] .media-icon--play,
.media-button--play:not([data-ended]):not([data-started]) .media-icon--play,
/* Play: started and not paused/ended → pause */
.media-button--play[data-started]:not([data-paused]):not([data-ended]) .media-icon--pause,
/* Mute: muted → volume off */
.media-button--mute[data-muted] .media-icon--volume-off,
/* Mute: volume low (not muted) → volume low */
.media-button--mute:not([data-muted])[data-volume-level="low"] .media-icon--volume-low,
/* Mute: volume high (not muted, not low) → volume high */
.media-button--mute:not([data-muted]):not([data-volume-level="low"]) .media-icon--volume-high {
display: block;
opacity: 1;
}
/* ==========================================================================
Root
========================================================================== */
.media-minimal-skin--audio {
--media-controls-background-color: oklch(1 0 0);
--media-controls-backdrop-filter: blur(16px) saturate(1.5);
--media-controls-border-color: oklch(0 0 0 / 0.1);
--media-controls-text-color: var(--media-color-primary, oklch(0 0 0));
--media-error-dialog-transition-duration: 250ms;
--media-error-dialog-transition-delay: 100ms;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
--media-tooltip-background-color: oklch(1 0 0);
--media-tooltip-border-color: oklch(0 0 0 / 0.1);
--media-tooltip-backdrop-filter: blur(16px) saturate(1.5);
--media-tooltip-text-color: currentColor;
--media-tooltip-side-offset: 0.75rem;
--media-tooltip-boundary-offset: 0.75rem;
--media-popover-background-color: var(--media-tooltip-background-color);
--media-popover-border-color: var(--media-tooltip-border-color);
--media-popover-backdrop-filter: var(--media-tooltip-backdrop-filter);
--media-popover-side-offset: var(--media-tooltip-side-offset);
--media-popover-boundary-offset: var(--media-tooltip-boundary-offset);
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-color-scheme: dark) {
--media-controls-background-color: oklch(0 0 0);
--media-controls-border-color: oklch(1 0 0 / 0.1);
--media-controls-text-color: var(--media-color-primary, oklch(1 0 0));
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-tooltip-background-color: oklch(1 0 0);
}
@media (prefers-color-scheme: dark) and ((prefers-reduced-transparency: reduce) or (prefers-contrast: more)) {
--media-tooltip-background-color: oklch(0 0 0);
}
}
/* ==========================================================================
Error Dialog
========================================================================== */
.media-minimal-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: 1rem;
align-items: center;
padding-inline: 1.25rem 0.5rem;
background-color: oklch(from var(--media-controls-background-color) l c h / 1);
border-radius: calc(infinity * 1px);
transition-delay: var(--media-error-dialog-transition-delay);
transition-timing-function: ease-out;
transition-duration: var(--media-error-dialog-transition-duration);
transition-property: opacity, filter, scale;
}
.media-minimal-skin--audio .media-error[data-starting-style] .media-error__dialog,
.media-minimal-skin--audio .media-error[data-ending-style] .media-error__dialog {
opacity: 0;
filter: blur(4px);
scale: 0.95;
}
.media-minimal-skin--audio .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
.media-minimal-skin--audio .media-error__content {
display: flex;
flex: 1;
gap: 0.5rem;
align-items: center;
}
/* ==========================================================================
Controls
========================================================================== */
.media-minimal-skin--audio .media-controls {
gap: 0.5rem;
padding: 0.375rem;
color: var(--media-controls-text-color);
border-radius: var(--media-border-radius, 1rem);
box-shadow: 0 0 0 1px var(--media-controls-border-color);
}
/* ==========================================================================
Buffering (spinner over play button — overlay only, no layout change)
========================================================================== */
.media-minimal-skin--audio .media-button--play__wrapper {
position: relative;
display: inline-flex;
}
.media-minimal-skin--audio .media-button--play__wrapper .media-buffering-indicator {
color: inherit;
}
.media-minimal-skin--audio
.media-button--play__wrapper:has(.media-buffering-indicator[data-visible])
.media-button--play
.media-icon {
opacity: 0;
}
/* ==========================================================================
Popups & Animations
========================================================================== */
.media-minimal-skin--audio .media-popover--volume {
padding: 0 0 0 4rem;
background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%);
}
.media-minimal-skin--audio .media-slider__preview .media-slider__value {
bottom: 2.5rem;
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/audio-minimal-ui.js"></script>
<link rel="stylesheet" href="./player.css">
<audio-player>
<media-container class="**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,0.75rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 scrollbar-thin scrollbar-thumb-current/30 [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-auto [@media_(prefers-reduced-transparency:reduce)_or_(prefers-contrast:more)]:scrollbar-thumb-current/80 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] [--media-controls-background-color:oklch(1_0_0)] [--media-controls-backdrop-filter:blur(16px)_saturate(1.5)] [--media-controls-border-color:oklch(0_0_0/0.1)] [--media-controls-text-color:var(--media-color-primary,oklch(0_0_0))] [--media-error-dialog-transition-duration:250ms] [--media-error-dialog-transition-delay:100ms] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-tooltip-background-color:oklch(1_0_0)] [--media-tooltip-border-color:oklch(0_0_0/0.1)] [--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)] [--media-tooltip-text-color:currentColor] [--media-tooltip-side-offset:0.75rem] [--media-tooltip-boundary-offset:0.75rem] [--media-popover-background-color:var(--media-tooltip-background-color)] [--media-popover-border-color:var(--media-tooltip-border-color)] [--media-popover-backdrop-filter:var(--media-tooltip-backdrop-filter)] [--media-popover-side-offset:var(--media-tooltip-side-offset)] [--media-popover-boundary-offset:var(--media-tooltip-boundary-offset)] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-popup-transition-duration:0ms] dark:[--media-controls-background-color:oklch(0_0_0)] dark:[--media-controls-border-color:oklch(1_0_0/0.1)] dark:[--media-controls-text-color:var(--media-color-primary,oklch(1_0_0))] [@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(1_0_0)] contrast-more:[--media-tooltip-background-color:oklch(1_0_0)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(0_0_0)] dark:contrast-more:[--media-tooltip-background-color:oklch(0_0_0)]">
<audio src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"></audio>
<media-error-dialog class="peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none">
<div class="absolute inset-0 z-20 flex items-center gap-4 rounded-full px-5 pr-2 bg-(--media-controls-background-color) transition-[opacity,filter,scale] ease-out duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-xs group-data-starting-style/error:scale-95 group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-xs group-data-ending-style/error:scale-95 group-data-ending-style/error:delay-0">
<div class="flex flex-1 items-center gap-2">
<media-alert-dialog-title class="font-semibold leading-tight">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="opacity-70 wrap-anywhere"></media-alert-dialog-description>
</div>
<div class="flex gap-2 *:flex-1">
<media-alert-dialog-close class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<div class="peer/controls @container/media-controls flex items-center bg-(--media-controls-background-color) [backdrop-filter:var(--media-controls-backdrop-filter)] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) p-1.5 gap-2 rounded-(--media-border-radius,1rem) peer-data-open/error:**:invisible text-(--media-controls-text-color) ring-1 ring-(color:--media-controls-border-color)">
<media-tooltip-group>
<div class="flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5">
<span class="group/play inline-flex relative">
<media-buffering-indicator class="peer/play-buffering absolute inset-0 z-10 hidden place-content-center pointer-events-none text-inherit not-data-visible:[--media-spinner-animation:none] data-visible:grid">
<media-icon name="spinner" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
</media-buffering-indicator>
<media-play-button commandfor="play-tooltip" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 group peer-data-visible/play-buffering:[&>svg]:opacity-0">
<media-icon name="restart" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-ended:block group-data-ended:opacity-100"></media-icon>
<media-icon name="play" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100"></media-icon>
<media-icon name="pause" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100"></media-icon>
</media-play-button>
<media-tooltip id="play-tooltip" side="top" boundary="viewport" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
</span>
<media-seek-button commandfor="seek-backward-tooltip" seconds="-10" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90">
<span class="relative grid">
<media-icon name="seek" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out [scale:-1_1]"></media-icon>
<span class="text-[10px] font-medium tracking-tighter tabular-nums absolute -left-px -bottom-0.75">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-backward-tooltip" side="top" boundary="viewport" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
<media-seek-button commandfor="seek-forward-tooltip" seconds="10" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90">
<span class="relative grid">
<media-icon name="seek" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out"></media-icon>
<span class="text-[10px] font-medium tracking-tighter tabular-nums absolute -right-px -bottom-0.75">10</span>
</span>
</media-seek-button>
<media-tooltip id="seek-forward-tooltip" side="top" boundary="viewport" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap data-open:flex data-open:items-center data-open:gap-1 bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/20 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="min-w-[1.5em] -mr-1 p-[0.1em] bg-current/15 text-[90%] font-semibold font-[inherit] leading-[1.25] text-center rounded"></media-tooltip-shortcut>
</media-tooltip>
</div>
<div class="@container flex flex-row-reverse items-center flex-1 gap-3 @2xl/media-root:flex-row">
<media-time-group class="flex items-center gap-1">
<media-time toggle type="current" class="hidden tabular-nums cursor-pointer rounded-sm outline-2 outline-transparent -outline-offset-2 transition-[outline-color,outline-offset] duration-100 ease-out supports-[corner-shape:squircle]:rounded-4 supports-[corner-shape:squircle]:[corner-shape:squircle] focus-visible:outline-current focus-visible:outline-offset-2 @2xl/media-root:inline"></media-time>
<media-time-separator class="hidden @2xl/media-root:inline @2xl/media-root:text-current/60"></media-time-separator>
<media-time type="duration" class="tabular-nums @2xl/media-root:text-current/60"></media-time>
</media-time-group>
<media-time-slider class="group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]">
<media-slider-track class="relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-0.75 data-[orientation=vertical]:w-0.75 data-[orientation=vertical]:h-full">
<media-slider-fill class="absolute rounded-[inherit] pointer-events-none bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)"></media-slider-fill>
<media-slider-buffer class="absolute rounded-[inherit] pointer-events-none bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer,0) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)"></media-slider-buffer>
</media-slider-track>
<media-slider-thumb class="z-10 absolute size-3 -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,scale,outline-offset] duration-150 ease-out select-none outline-2 outline-transparent -outline-offset-2 focus-visible:outline-current focus-visible:outline-offset-2 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill,0))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))] opacity-0 scale-70 origin-center group-hover/slider:opacity-100 group-hover/slider:scale-100 group-focus-within/slider:opacity-100 group-focus-within/slider:scale-100"></media-slider-thumb>
<media-slider-preview class="group/preview before:block before:bg-current/35 before:opacity-0 before:scale-50 before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 data-[orientation=horizontal]:before:min-w-px data-[orientation=horizontal]:before:h-5 data-[orientation=vertical]:before:w-5 data-[orientation=vertical]:before:min-h-px peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden">
<media-slider-value type="pointer" class="absolute bottom-6 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none bottom-10"></media-slider-value>
</media-slider-preview>
</media-time-slider>
</div>
<div class="flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5">
<media-playback-rate-button commandfor="playback-rate-menu" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 tabular-nums after:w-[4ch] after:content-[attr(data-rate)_'×'] data-[inline-rate-label]:after:content-none">
</media-playback-rate-button>
<media-menu id="playback-rate-menu" side="top" align="center" boundary="viewport" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:250ms] [--menu-max-height:14rem] [--menu-padding:0.5rem] [--menu-border-radius:1rem] [--menu-item-border-radius:calc(var(--menu-border-radius)_-_var(--menu-padding))] motion-reduce:[--menu-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-[min(var(--media-popover-available-height,var(--menu-max-height)),var(--menu-max-height))] bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] box-border rounded-(--menu-border-radius) p-(--menu-padding) overscroll-none overflow-auto!">
<media-playback-rate-radio-group class="flex flex-col gap-0.5 supports-[top:anchor(top)]:before:absolute supports-[top:anchor(top)]:before:[position-anchor:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:before:[inset:anchor(inside)] supports-[top:anchor(top)]:before:pointer-events-none supports-[top:anchor(top)]:before:bg-current/10 supports-[top:anchor(top)]:before:rounded-(--menu-item-border-radius) supports-[top:anchor(top)]:before:transition-[inset] supports-[top:anchor(top)]:before:duration-100 supports-[top:anchor(top)]:before:ease-in-out group-data-[menu-view-state=inactive]/menu-root-view:before:hidden">
<template>
<media-menu-radio-item class="flex cursor-pointer select-none items-center gap-1.5 rounded-(--menu-item-border-radius) py-1.5 px-2 text-left text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-colors duration-100 ease-out hover:bg-current/10 data-highlighted:bg-current/10 supports-[top:anchor(top)]:hover:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:hover:bg-transparent supports-[top:anchor(top)]:data-highlighted:[anchor-name:--media-menu-item-highlight-anchor] supports-[top:anchor(top)]:data-highlighted:bg-transparent focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="ml-auto -mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100">
<media-icon name="check" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out shrink-0 text-current/50 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]"></media-icon>
</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
</media-menu>
<media-mute-button commandfor="audio-volume-popover" class="flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10 grid w-[2.375rem] aspect-square p-0 active:scale-90 group">
<media-icon name="volume-off" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-data-muted:block group-data-muted:opacity-100"></media-icon>
<media-icon name="volume-low" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100"></media-icon>
<media-icon name="volume-high" family="minimal" class="block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100"></media-icon>
</media-mute-button>
<media-popover id="audio-volume-popover" open-on-hover delay="200" close-delay="100" side="left" boundary="viewport" class="[--media-popup-base-transition:opacity_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),filter_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),transform_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration),scale_var(--media-popup-transition-timing-function)_var(--media-popup-transition-duration)] [--popup-translate-distance:0.5rem] m-0 border-0 text-inherit overflow-visible [transition:var(--media-popup-transition,var(--media-popup-base-transition))] data-starting-style:opacity-0 data-starting-style:blur-xs data-starting-style:scale-95 data-starting-style:[transform:translate(var(--popup-translate-x-distance,0),var(--popup-translate-y-distance,0))] data-ending-style:opacity-0 data-ending-style:blur-xs data-ending-style:scale-95 data-ending-style:transform-none data-ending-style:[transition-duration:max(0ms,calc(var(--media-popup-transition-duration)-50ms))] data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:[--popup-translate-y-distance:var(--popup-translate-distance)] data-[side=bottom]:[--popup-translate-y-distance:calc(var(--popup-translate-distance)*-1)] data-[side=left]:[--popup-translate-x-distance:var(--popup-translate-distance)] data-[side=right]:[--popup-translate-x-distance:calc(var(--popup-translate-distance)*-1)] before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) p-0 pl-16 bg-transparent bg-gradient-to-l from-(--media-controls-background-color) from-80% to-transparent">
<media-volume-slider class="group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]" orientation="horizontal" thumb-alignment="edge">
<media-slider-track class="relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-0.75 data-[orientation=vertical]:w-0.75 data-[orientation=vertical]:h-full">
<media-slider-fill class="absolute rounded-[inherit] pointer-events-none bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:w-(--media-slider-pointer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=vertical]:h-(--media-slider-pointer)"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="z-10 absolute size-3 -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,scale,outline-offset] duration-150 ease-out select-none outline-2 outline-transparent -outline-offset-2 focus-visible:outline-current focus-visible:outline-offset-2 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill,0) group-data-dragging/slider:data-[orientation=horizontal]:left-(--media-slider-pointer) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill,0))] group-data-dragging/slider:data-[orientation=vertical]:top-[calc(100%-var(--media-slider-pointer))]"></media-slider-thumb>
</media-volume-slider>
</media-popover>
</div>
</media-tooltip-group>
</div>
</media-container>
</audio-player>