Customize skins
Learn the public customization surface for packaged Video.js skins and when to eject
Video.js v10 comes with two pre-built skins: Default and Minimal. Start with their public CSS custom properties. When you need to change controls, layout, or interactions, eject the closest skin by copying its code into your project and making it your own.
Basic customization
Set these CSS custom properties directly on the skin:
<video-skin style="--media-accent-color: #f5c518; --media-border-radius: 1rem">
<video src="video.mp4"></video>
</video-skin><VideoSkin
style={
{
'--media-accent-color': '#f5c518',
'--media-border-radius': '1rem',
} as React.CSSProperties
}
>
<Video src="video.mp4" />
</VideoSkin>| Property Name | Description | Type | Example |
|---|---|---|---|
--media-accent-color |
The color of slider fills and accented controls | <color> |
#f5c518 |
--media-accent-text-color |
The color of text and icons shown on the accent color | <color> |
black |
--media-border-radius |
The border radius of the media player | A valid border-radius value | 1rem |
--media-scale-unit |
The base sizing unit for skin spacing and icons | <length> |
1rem |
If you omit --media-accent-text-color, the skins choose a contrasting text color from --media-accent-color using contrast-color(). You only need to set it when you want to override that result.
Set the audio color scheme
The audio skins adapt their main palette for light and dark mode using light-dark(). It uses the inherited color-scheme, so set color-scheme on the player or an ancestor:
:root {
color-scheme: light dark;
}The video skins keep their dark control presentation in both color schemes.
Scale with the document font size
Vanilla CSS and Tailwind skins use --media-scale-unit as the base for control spacing and icon sizes. Vanilla CSS skins also use it for font sizes. It defaults to 16px, so player sizing does not change with the document root font size or the app’s Tailwind spacing scale. Set it to 1rem on the player or an ancestor to make the UI scale with the document root font size:
.player {
--media-scale-unit: 1rem;
}You can also add your own class names to the skins.
Where packaged customization stops
The four properties above are the public styling controls for packaged skins. Don’t depend on the elements, classnames, or other custom properties inside a packaged skin. They may change between releases.
Packaged video skins also let you replace the poster through the poster slot.
Packaged video skins also let you replace the poster through the renderPoster prop.
See Poster.
For anything deeper, eject instead of reaching into the packaged skin. There are no packaged-skin options for adding, removing, or rearranging controls, or replacing its built-in feedback and interactions.
Eject a skin
Eject as soon as the packaged skin no longer matches the player you need. Once ejected, removing a control is removing a component, and changing an overlay or interaction is editing code you own.
An ejected skin is app code, so its markup, class names, and styles are yours to change. If none of the packaged skins is a useful starting point, build a custom UI from the player, container, and individual UI components instead.
Soon, we’ll have a CLI that ejects skins in your preferred framework and style. For now, we invite you to try these copy-paste-ready implementations.
To eject a skin:
- Choose the closest implementation below.
- Copy every file shown for your framework into your project.
- Replace the packaged skin with the copied component or player markup, then edit the files you own.
Default video skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { audioText, captionsText, playbackRateText, qualityText, settingsText, speedText } from '@videojs/core/i18n/text/menu';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, QualityIcon, RestartIcon, SpeechIcon, SpeedIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { Poster, useTranslator, Container, usePlayer, AirPlayButton, useAudioTrackOptions, AudioTrackRadioGroup, BufferingIndicator, CaptionsButton, CaptionsRadioGroup, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, PlaybackRateRadioGroup, Popover, useQualityOptions, QualityRadioGroup, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './player.css';
import { Player } from './player';
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 t = useTranslator();
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">
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<Menu.Trigger aria-label={t(settingsText)} className="media-button--settings" render={<Button />}>
<GearIcon className="media-icon media-icon--settings" />
</Menu.Trigger>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label>{t(settingsText)}</Tooltip.Label>
</Tooltip.Popup>
</Tooltip.Root>
<Menu.Popup className="media-surface media-popover media-menu media-menu--settings">
<Menu.Content className="media-menu__content">
{hasQuality ? (
<Menu.Root>
<Menu.Trigger
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<QualityIcon className="media-icon" />
<span>{t(qualityText)}</span>
<span className="media-menu__hint">
<bdi dir="auto" className="media-menu__hint-label">
{quality.selectedLabel}
</bdi>
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Item className="media-menu__back">
<MenuChevron flipped />
{t(qualityText)}
</Menu.Item>
<Menu.Separator className="media-menu__separator" />
<QualityRadioGroup
className="media-menu__group"
aria-label={t(qualityText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<bdi dir="auto">
{item.label}
{item.tier ? <sup className="media-menu__tier">{item.tier}</sup> : null}
</bdi>
{item.badge ? <span className="media-badge">{item.badge}</span> : null}
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
</Menu.Content>
</Menu.Root>
) : null}
{hasAudioTrack ? (
<Menu.Root>
<Menu.Trigger
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<SpeechIcon className="media-icon" />
<span>{t(audioText)}</span>
<span className="media-menu__hint">
<bdi dir="auto" className="media-menu__hint-label">
{audioTrack.selectedLabel}
</bdi>
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Item className="media-menu__back">
<MenuChevron flipped />
{t(audioText)}
</Menu.Item>
<Menu.Separator className="media-menu__separator" />
<AudioTrackRadioGroup
className="media-menu__group"
aria-label={t(audioText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<bdi dir="auto">{item.label}</bdi>
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
</Menu.Content>
</Menu.Root>
) : null}
{hasPlaybackRate ? (
<Menu.Root>
<Menu.Trigger
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<SpeedIcon className="media-icon" />
<span>{t(speedText)}</span>
<span className="media-menu__hint">
<bdi dir="auto" className="media-menu__hint-label">
{playbackRate.selectedLabel}
</bdi>
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Item className="media-menu__back">
<MenuChevron flipped />
{t(speedText)}
</Menu.Item>
<Menu.Separator className="media-menu__separator" />
<PlaybackRateRadioGroup
className="media-menu__group"
aria-label={t(playbackRateText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<bdi dir="auto">{item.label}</bdi>
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
</Menu.Content>
</Menu.Root>
) : null}
{hasCaptions ? (
<Menu.Root>
<Menu.Trigger
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<CaptionsOffIcon className="media-icon" />
<span>{t(captionsText)}</span>
<span className="media-menu__hint">
<bdi dir="auto" className="media-menu__hint-label">
{captions.selectedLabel}
</bdi>
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Item className="media-menu__back">
<MenuChevron flipped />
{t(captionsText)}
</Menu.Item>
<Menu.Separator className="media-menu__separator" />
<CaptionsRadioGroup
className="media-menu__group"
aria-label={t(captionsText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<bdi dir="auto">{item.label}</bdi>
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
</Menu.Content>
</Menu.Root>
) : null}
</Menu.Content>
</Menu.Popup>
</Menu.Root>
);
}
function CastControl() {
return (
<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>
);
}
function AirPlayControl() {
return (
<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>
);
}
function PiPControl() {
return (
<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>
);
}
function FullscreenControl() {
return (
<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>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
export interface VideoPlayerProps {
src: string;
poster?: string | undefined;
style?: CSSProperties;
className?: string;
renderPoster?: Poster.Props['render'];
}
/**
* @example
* ```tsx
* <VideoPlayer
* src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
* poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp"
* />
* ```
*/
export function VideoPlayer({ src, poster, className, renderPoster, style, ...rest }: VideoPlayerProps): ReactNode {
return (
<Player poster={poster}>
<Container className={`media-default-skin media-default-skin--video ${className ?? ''}`} style={style} {...rest}>
<Video src={src} playsInline />
<Poster render={renderPoster} />
<BufferingIndicator
render={(props) => (
<div {...props} className="media-buffering-indicator">
<SpinnerIcon className="media-icon" />
</div>
)}
/>
<ErrorDialog.Root>
<ErrorDialog.Backdrop className="media-overlay media-error-backdrop" />
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog media-surface">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title"></ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--primary"></ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root>
<Controls.Backdrop className="media-overlay" />
<Controls.Content className="media-surface media-controls media-controls--root">
<Tooltip.Provider>
<div className="media-surface media-controls media-controls--primary">
<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>
<VolumePopover />
</div>
<div className="media-time-controls">
<Time.Value type="current" className="media-time" />
<TimeSlider.Root className="media-slider">
<TimeSlider.Chapters
className="media-slider__chapters"
renderChapter={(props) => (
<div {...props} className={`${props.className} media-slider__chapter`}>
<TimeSlider.Track className="media-slider__track media-slider__chapter-track">
<TimeSlider.Buffer className="media-slider__buffer" />
<TimeSlider.Fill className="media-slider__fill" />
</TimeSlider.Track>
</div>
)}
/>
<TimeSlider.Thumb className="media-slider__thumb" />
<TimeSlider.Preview overflow="visible" className="media-slider__preview">
<div className="media-surface media-thumbnail media-slider__thumbnail">
<Slider.Thumbnail className="media-thumbnail__image" />
<SpinnerIcon className="media-thumbnail__spinner media-icon" />
</div>
<div className="media-slider__value">
<TimeSlider.ChapterTitle className="media-slider__chapter-title" />
<TimeSlider.Value type="pointer" className="media-time" />
</div>
</TimeSlider.Preview>
</TimeSlider.Root>
<Time.Value toggle type="remaining" className="media-time" />
</div>
<div className="media-button-group">
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CaptionsButton className="media-button--captions" render={<Button />}>
<CaptionsOffIcon className="media-icon media-icon--captions-off" />
<CaptionsOnIcon className="media-icon media-icon--captions-on" />
</CaptionsButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<SettingsMenu />
</div>
</div>
<div className="media-surface media-controls media-controls--secondary">
<div className="media-button-group">
<CastControl />
<AirPlayControl />
<PiPControl />
<FullscreenControl />
</div>
</div>
</Tooltip.Provider>
</Controls.Content>
</Controls.Root>
{/* 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 Indicators */}
<StatusAnnouncer className="media-sr-only" />
<div className="media-input-indicator-overlay">
<VolumeIndicator.Root className="media-surface media-volume-indicator">
<VolumeIndicator.Fill className="media-volume-indicator__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-volume-indicator__value" />
</VolumeIndicator.Fill>
</VolumeIndicator.Root>
<StatusIndicator.Root
actions={TOP_STATUS_ACTIONS}
className="media-surface media-status-indicator media-status-indicator--state"
>
<div className="media-status-indicator__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-status-indicator__value" />
</div>
</StatusIndicator.Root>
<SeekIndicator.Root className="media-seek-indicator">
<ChevronIcon className="media-icon media-icon--seek" />
<SeekIndicator.Value className="media-seek-indicator__value" />
</SeekIndicator.Root>
<StatusIndicator.Root
actions={CENTER_STATUS_ACTIONS}
className="media-status-indicator media-status-indicator--playback"
>
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</StatusIndicator.Root>
</div>
</Container>
</Player>
);
}
// ================================================================
// 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 volumeUnavailable = usePlayer((s) => s.volumeAvailability !== 'available');
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 (volumeUnavailable) 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>
);
}
import { createPlayer } from '@videojs/react';
import { videoFeatures } from '@videojs/react/video';
export const { Player } = createPlayer({ features: videoFeatures });
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-default-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 1.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius, 1.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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 1px 0 0 var(--media-surface-inner-border-color),
inset 0 0 0 1px oklch(from var(--media-surface-inner-border-color) l c h / calc(alpha * 0.5));
}
}
.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-container-border-radius);
}
.media-default-skin video {
border-radius: inherit;
}
.media-default-skin:fullscreen ::slotted(video),
.media-default-skin:fullscreen video {
object-fit: contain;
}
.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-backdrop {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-default-skin .media-controls-backdrop[data-visible],
.media-default-skin .media-error-backdrop[data-open] {
opacity: 1;
}
.media-default-skin:has(.media-buffering-indicator[data-visible]) .media-controls-backdrop {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-default-skin .media-error-backdrop[data-open] {
backdrop-filter: blur(16px) saturate(1.5);
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-default-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-default-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * var(--media-base-boundary-offset, 2));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
border-radius: calc(Infinity * 1px);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: calc(var(--media-spacing) * 2.5);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row-reverse;
}
> .media-time:last-child {
@container media-time-controls (width < 16rem) {
display: none;
}
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
}
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
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, color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-default-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-default-skin .media-button {
scale: 1;
transition-property: background-color, color;
will-change: auto;
}
}
.media-default-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-badge {
padding: calc(var(--media-spacing) * 0.5) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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);
}
.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,
.media-default-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-default-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.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]) {
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-container-border-radius);
}
.media-default-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
.media-default-skin:fullscreen media-poster ::slotted(img),
.media-default-skin:fullscreen media-poster img,
.media-default-skin:fullscreen > img {
object-fit: contain;
}
.media-default-skin .media-thumbnail {
position: relative;
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: calc(var(--media-spacing) * 3);
.media-thumbnail__image {
position: relative;
display: block;
max-width: var(--media-thumbnail-max-width);
max-height: var(--media-thumbnail-max-height);
overflow: clip;
border-radius: inherit;
&::after {
position: absolute;
inset: 0;
content: "";
background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.1), oklch(0 0 0 / 0));
border-radius: inherit;
}
}
.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;
}
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
&:has(.media-thumbnail__image[data-loading]) {
width: var(--media-thumbnail-max-width);
max-width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
.media-thumbnail__image {
opacity: 0;
}
.media-thumbnail__spinner {
opacity: 1;
}
}
}
.media-default-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: var(--media-track-border-radius);
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: inherit;
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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;
scale: 0.8;
translate: -50% -50%;
&[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: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
opacity: 1;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (hover: hover) and (pointer: fine) {
&:hover {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
&::after {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 36;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: var(--media-max-size);
height: calc(var(--media-spacing) * 1);
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: 50%;
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 9));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 10.5));
display: flex;
flex-direction: column;
align-items: center;
}
.media-slider__chapter-title {
min-width: 0;
max-width: var(--media-max-size);
padding-inline: calc(var(--media-spacing) * 6);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
width: calc(var(--media-spacing) * 1);
height: calc(var(--media-spacing) * 1);
pointer-events: none;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-shadow-current-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail),
.media-slider__preview[data-pointing]:not([data-dragging])::before {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(0.5 * var(--media-internal-scale-unit));
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(--media-popup-translate-x-distance, 0), var(--media-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"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 3) 0;
border-radius: calc(Infinity * 1px);
&:has(media-volume-slider[data-hidden]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
font-size: var(--media-font-size-base);
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: calc(var(--media-spacing) * 1);
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: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-default-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 3);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--media-menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin-block: calc(var(--media-spacing) * 1);
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
opacity: 0;
.media-icon {
filter: drop-shadow(0 1px 0 var(--media-shadow-current-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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
.media-default-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: calc(var(--media-spacing) * -2);
&:has(.media-controls[data-visible]) {
--media-caption-track-y: calc(var(--media-spacing) * -14);
}
}
.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);
}
.media-default-skin .media-input-indicator-overlay {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
color: oklch(1 0 0);
pointer-events: none;
}
.media-default-skin {
.media-volume-indicator,
.media-status-indicator--state {
--media-surface-background-color: oklch(0 0 0 / 0.25);
position: absolute;
top: calc(var(--media-spacing) * 3);
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-volume-indicator__content,
.media-status-indicator__content {
display: flex;
gap: calc(var(--media-spacing) * 2);
align-items: center;
justify-content: space-between;
width: 100%;
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
* {
mix-blend-mode: difference;
}
}
.media-icon {
display: none;
flex-shrink: 0;
}
.media-volume-indicator__value,
.media-status-indicator__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);
}
&[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;
}
}
&[data-ending-style] {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -25%;
}
}
}
.media-seek-indicator,
.media-status-indicator--playback {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: calc(var(--media-spacing) * 4);
text-align: center;
}
}
.media-default-skin .media-volume-indicator {
width: min(80%, calc(var(--media-spacing) * 48));
transform: translateX(0);
.media-volume-indicator__content {
background-image: linear-gradient(currentColor, currentColor);
background-repeat: no-repeat;
background-position: left;
background-size: var(--media-volume-fill, 0%) 100%;
border-radius: inherit;
transition: background-size 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]:not([data-starting-style], [data-ending-style]),
&[data-max]:not([data-starting-style], [data-ending-style]) {
transform: translateX(0.25px);
transition: transform 300ms linear(0, -24 20%, 16 40%, -8 60%, 4 80%, 1);
}
}
}
.media-default-skin .media-status-indicator--state {
&[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;
}
}
.media-default-skin .media-status-indicator--playback {
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;
.media-icon {
grid-area: 1 / 1;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
opacity: 0;
scale: 0;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
&.media-icon--play {
translate: 1px 0;
}
}
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
opacity: 1;
scale: 1;
}
&[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;
&[data-starting-style],
&[data-ending-style],
.media-icon {
scale: 1;
}
.media-icon {
transition-duration: 50ms;
transition-property: opacity;
}
}
}
.media-default-skin .media-seek-indicator {
gap: calc(var(--media-spacing) * 1);
.media-seek-indicator__value {
font-variant-numeric: tabular-nums;
}
@container media-root (width > 24rem) {
padding: calc(var(--media-spacing) * 6);
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
.media-icon--seek {
display: block;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[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 shared
by the HTML and React skins.
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon,
.media-button--mute .media-icon,
.media-button--fullscreen .media-icon,
.media-button--pip .media-icon,
.media-button--cast .media-icon,
.media-button--airplay .media-icon,
.media-button--captions .media-icon {
opacity: 0;
}
.media-button--play .media-icon {
scale: 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 {
opacity: 1;
scale: 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;
}
@media (prefers-reduced-motion: reduce) {
.media-button--airplay {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
}
/* Video-specific container styles */
.media-default-skin--video {
--media-default-accent-color: oklch(1 0 0);
--media-border-color: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.15));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-video-border-radius: var(--media-container-border-radius);
--media-surface-background-color: oklch(1 0 0 / 0.1);
--media-surface-inner-border-color: oklch(1 0 0 / 0.1);
--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-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: ease-out;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
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-error__dialog {
scale: 1;
transition-property: opacity;
}
}
@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--root: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-container-border-radius: 0;
&::after {
display: none;
}
@media (width >= 1280px) {
--media-scale: 1.25;
}
@media (width >= 1536px) {
--media-scale: 1.5;
}
@media (width >= 1920px) {
--media-scale: 1.75;
}
}
* {
--media-focus-ring-color: oklch(1 0 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: calc(var(--media-spacing) * 3);
width: 100%;
max-width: calc(var(--media-spacing) * 72);
padding: calc(var(--media-spacing) * 3);
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.25);
border-radius: calc(var(--media-spacing) * 7);
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.95;
}
.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: calc(var(--media-spacing) * 2);
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 1.5);
text-shadow: inherit;
}
.media-default-skin--video .media-error__title {
font-size: var(--media-font-size-medium);
}
.media-default-skin--video .media-slider__value {
text-shadow: 0 1px 0 var(--media-shadow-current-color);
}
/* Controls (hide/show behavior) */
.media-default-skin--video .media-controls--root {
--media-inset-factor: 2;
--media-inset: calc(var(--media-spacing) * var(--media-inset-factor));
--media-base-boundary-offset: var(--media-inset-factor);
z-index: 10;
display: contents;
color: oklch(1 0 0);
transition-timing-function: var(--media-controls-transition-timing-function);
/* Speed up the entry transition */
transition-duration: calc(var(--media-controls-transition-duration) / 2);
.media-controls--primary,
.media-controls--secondary {
position: absolute;
}
/* Primary must be higher for the preview/thumbnail */
.media-controls--primary {
z-index: 20;
}
.media-controls--secondary {
z-index: 10;
}
.media-controls--primary {
inset-inline: var(--media-inset);
bottom: var(--media-inset);
transform-origin: bottom;
}
.media-controls--secondary {
top: var(--media-inset);
right: var(--media-inset);
container-type: normal;
transform-origin: top;
}
.media-time-controls {
flex: 1;
padding-inline: calc(var(--media-spacing) * 2);
}
@container media-root (width < 32rem) {
.media-controls--primary,
.media-controls--secondary {
transition-timing-function: inherit;
transition-duration: inherit;
@media (pointer: fine) {
transition-property: filter, opacity, scale, translate;
}
@media (pointer: coarse) {
transition-property: opacity, scale, translate;
}
}
&:after {
display: none;
}
&:not([data-visible]) {
.media-controls--primary,
.media-controls--secondary {
pointer-events: none;
opacity: 0;
scale: 0.95;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
}
.media-controls--primary {
@media (prefers-reduced-motion: no-preference) {
translate: 0 4px;
}
}
.media-controls--secondary {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -4px;
}
}
}
.media-button--captions {
display: none;
}
}
@container media-root (width >= 32rem) {
position: absolute;
inset-inline: var(--media-inset);
bottom: var(--media-inset);
display: flex;
transform-origin: bottom;
.media-controls--primary,
.media-controls--secondary {
display: contents;
&::after {
display: none;
}
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
scale: 0.95;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 4px;
}
}
.media-time-controls {
padding-inline: calc(var(--media-spacing) * 3);
}
}
@media (pointer: fine) {
transition-property: filter, opacity, scale, translate;
}
@media (pointer: coarse) {
transition-property: opacity, scale, translate;
}
@container media-root (width > 42rem) {
--media-inset-factor: 3;
}
}
.media-default-skin--video:has(.media-error[data-open]) .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-default-skin--video:has(.media-controls--root:not([data-visible])) {
cursor: none;
}
/* Sliders */
.media-default-skin--video .media-slider__track {
background-color: oklch(1 0 0 / 0.2);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/video.js"></script>
<link rel="stylesheet" href="./player.css">
<video-player poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp">
<media-container class="media-default-skin media-default-skin--video">
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" playsinline></video>
<media-poster>
<img alt="" decoding="async" />
</media-poster>
<media-buffering-indicator class="media-buffering-indicator">
<media-icon name="spinner" class="media-icon"></media-icon>
</media-buffering-indicator>
<media-error-dialog>
<media-dialog-backdrop class="media-overlay media-error-backdrop"></media-dialog-backdrop>
<media-dialog-popup class="media-error">
<div class="media-error__dialog media-surface">
<div class="media-error__content">
<media-dialog-title class="media-error__title"></media-dialog-title>
<media-dialog-description class="media-error__description"></media-dialog-description>
</div>
<div class="media-error__actions">
<media-dialog-close class="media-button media-button--primary"></media-dialog-close>
</div>
</div>
</media-dialog-popup>
</media-error-dialog>
<media-controls>
<media-controls-backdrop class="media-overlay"></media-controls-backdrop>
<media-controls-content class="media-surface media-controls media-controls--root">
<media-tooltip-group>
<div class="media-surface media-controls media-controls--primary">
<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-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>
</div>
<div class="media-time-controls">
<media-time type="current" class="media-time"></media-time>
<media-time-slider class="media-slider">
<media-time-slider-chapters class="media-slider__chapters">
<template>
<div class="media-slider__chapter">
<media-slider-track class="media-slider__track media-slider__chapter-track">
<media-slider-buffer class="media-slider__buffer"></media-slider-buffer>
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
</div>
</template>
</media-time-slider-chapters>
<media-slider-thumb class="media-slider__thumb"></media-slider-thumb>
<media-slider-preview overflow="visible" class="media-slider__preview">
<div class="media-surface media-thumbnail media-slider__thumbnail">
<media-slider-thumbnail class="media-thumbnail__image"></media-slider-thumbnail>
<media-icon name="spinner" class="media-thumbnail__spinner media-icon"></media-icon>
</div>
<div class="media-slider__value">
<media-time-slider-chapter-title class="media-slider__chapter-title"></media-time-slider-chapter-title>
<media-slider-value type="pointer" class="media-time"></media-slider-value>
</div>
</media-slider-preview>
</media-time-slider>
<media-time toggle type="remaining" class="media-time"></media-time>
</div>
<div class="media-button-group">
<media-captions-button commandfor="captions-tooltip" class="media-button media-button--subtle media-button--icon media-button--captions">
<media-icon name="captions-off" class="media-icon media-icon--captions-off"></media-icon>
<media-icon name="captions-on" class="media-icon media-icon--captions-on"></media-icon>
</media-captions-button>
<media-tooltip id="captions-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>
<button id="settings-trigger" commandfor="settings-menu" aria-labelledby="settings-label" class="media-button media-button--subtle media-button--icon media-button--settings">
<media-icon name="gear" class="media-icon media-icon--settings"></media-icon>
<media-text token="menu.settings" id="settings-label" class="media-sr-only">Settings</media-text>
</button>
<media-menu id="settings-menu" side="top" align="center" class="media-surface media-popover media-menu media-menu--settings">
<media-menu-content class="media-menu__content">
<media-menu-item commandfor="settings-quality-menu" class="media-menu__item media-menu__item--submenu">
<media-icon name="switches" class="media-icon"></media-icon>
<media-text token="menu.quality">Quality</media-text>
<span class="media-menu__hint">
<bdi data-part="hint" dir="auto" class="media-menu__hint-label"></bdi>
<media-icon name="chevron" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-audio-menu" class="media-menu__item media-menu__item--submenu">
<media-icon name="speech" class="media-icon"></media-icon>
<media-text token="menu.audio">Audio</media-text>
<span class="media-menu__hint">
<bdi data-part="hint" dir="auto" class="media-menu__hint-label"></bdi>
<media-icon name="chevron" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-speed-menu" class="media-menu__item media-menu__item--submenu">
<media-icon name="speed" class="media-icon"></media-icon>
<media-text token="menu.speed">Speed</media-text>
<span class="media-menu__hint">
<bdi data-part="hint" dir="auto" class="media-menu__hint-label"></bdi>
<media-icon name="chevron" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
<media-menu-item commandfor="settings-captions-menu" class="media-menu__item media-menu__item--submenu">
<media-icon name="captions-off" class="media-icon"></media-icon>
<media-text token="menu.captions">Captions</media-text>
<span class="media-menu__hint">
<bdi data-part="hint" dir="auto" class="media-menu__hint-label"></bdi>
<media-icon name="chevron" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
</media-menu-content>
<media-menu-content id="settings-quality-menu" class="media-menu__panel">
<media-menu-item class="media-menu__back">
<media-icon name="chevron" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
<media-text token="menu.quality">Quality</media-text>
</media-menu-item>
<div class="media-menu__separator"></div>
<media-quality-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span>
<bdi data-part="label" dir="auto"></bdi>
<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-content>
<media-menu-content id="settings-audio-menu" class="media-menu__panel">
<media-menu-item class="media-menu__back">
<media-icon name="chevron" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
<media-text token="menu.audio">Audio</media-text>
</media-menu-item>
<div class="media-menu__separator"></div>
<media-audio-track-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<bdi data-part="label" dir="auto"></bdi>
<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-content>
<media-menu-content id="settings-speed-menu" class="media-menu__panel">
<media-menu-item class="media-menu__back">
<media-icon name="chevron" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
<media-text token="menu.speed">Speed</media-text>
</media-menu-item>
<div class="media-menu__separator"></div>
<media-playback-rate-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<bdi data-part="label" dir="auto"></bdi>
<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-content>
<media-menu-content id="settings-captions-menu" class="media-menu__panel">
<media-menu-item class="media-menu__back">
<media-icon name="chevron" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
<media-text token="menu.captions">Captions</media-text>
</media-menu-item>
<div class="media-menu__separator"></div>
<media-captions-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<bdi data-part="label" dir="auto"></bdi>
<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-content>
</media-menu>
<media-tooltip id="settings-tooltip" trigger="settings-trigger" side="top" class="media-surface media-tooltip">
<media-text token="menu.settings">Settings</media-text>
</media-tooltip>
</div>
</div>
<div class="media-surface media-controls media-controls--secondary">
<div class="media-button-group">
<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>
</div>
</media-tooltip-group>
</media-controls-content>
</media-controls>
<!-- 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 Indicators -->
<media-status-announcer class="media-sr-only"></media-status-announcer>
<div class="media-input-indicator-overlay">
<media-volume-indicator hidden class="media-surface media-volume-indicator">
<media-volume-indicator-fill class="media-volume-indicator__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-volume-indicator__value"></media-volume-indicator-value>
</media-volume-indicator-fill>
</media-volume-indicator>
<media-status-indicator
hidden
actions="toggleSubtitles toggleFullscreen togglePictureInPicture"
class="media-surface media-status-indicator media-status-indicator--state"
>
<div class="media-status-indicator__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-status-indicator__value"></media-status-indicator-value>
</div>
</media-status-indicator>
<media-seek-indicator hidden class="media-seek-indicator">
<media-icon name="chevron" class="media-icon media-icon--seek"></media-icon>
<media-seek-indicator-value class="media-seek-indicator__value"></media-seek-indicator-value>
</media-seek-indicator>
<media-status-indicator hidden actions="togglePaused" class="media-status-indicator media-status-indicator--playback">
<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,
media-i18n,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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.
*/
media-container video,
media-container [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
media-container 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,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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 the volume popover when its slider is unavailable. */
.media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-default-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 1.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius, 1.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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 1px 0 0 var(--media-surface-inner-border-color),
inset 0 0 0 1px oklch(from var(--media-surface-inner-border-color) l c h / calc(alpha * 0.5));
}
}
.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-container-border-radius);
}
.media-default-skin video {
border-radius: inherit;
}
.media-default-skin:fullscreen ::slotted(video),
.media-default-skin:fullscreen video {
object-fit: contain;
}
.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-backdrop {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-default-skin .media-controls-backdrop[data-visible],
.media-default-skin .media-error-backdrop[data-open] {
opacity: 1;
}
.media-default-skin:has(.media-buffering-indicator[data-visible]) .media-controls-backdrop {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-default-skin .media-error-backdrop[data-open] {
backdrop-filter: blur(16px) saturate(1.5);
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-default-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-default-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * var(--media-base-boundary-offset, 2));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
border-radius: calc(Infinity * 1px);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: calc(var(--media-spacing) * 2.5);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row-reverse;
}
> .media-time:last-child {
@container media-time-controls (width < 16rem) {
display: none;
}
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
}
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
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, color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-default-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-default-skin .media-button {
scale: 1;
transition-property: background-color, color;
will-change: auto;
}
}
.media-default-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-badge {
padding: calc(var(--media-spacing) * 0.5) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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);
}
.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,
.media-default-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-default-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.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]) {
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-container-border-radius);
}
.media-default-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
.media-default-skin:fullscreen media-poster ::slotted(img),
.media-default-skin:fullscreen media-poster img,
.media-default-skin:fullscreen > img {
object-fit: contain;
}
.media-default-skin .media-thumbnail {
position: relative;
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: calc(var(--media-spacing) * 3);
.media-thumbnail__image {
position: relative;
display: block;
max-width: var(--media-thumbnail-max-width);
max-height: var(--media-thumbnail-max-height);
overflow: clip;
border-radius: inherit;
&::after {
position: absolute;
inset: 0;
content: "";
background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.1), oklch(0 0 0 / 0));
border-radius: inherit;
}
}
.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;
}
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
&:has(.media-thumbnail__image[data-loading]) {
width: var(--media-thumbnail-max-width);
max-width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
.media-thumbnail__image {
opacity: 0;
}
.media-thumbnail__spinner {
opacity: 1;
}
}
}
.media-default-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: var(--media-track-border-radius);
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: inherit;
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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;
scale: 0.8;
translate: -50% -50%;
&[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: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
opacity: 1;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (hover: hover) and (pointer: fine) {
&:hover {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
&::after {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 36;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: var(--media-max-size);
height: calc(var(--media-spacing) * 1);
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: 50%;
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 9));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 10.5));
display: flex;
flex-direction: column;
align-items: center;
}
.media-slider__chapter-title {
min-width: 0;
max-width: var(--media-max-size);
padding-inline: calc(var(--media-spacing) * 6);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
width: calc(var(--media-spacing) * 1);
height: calc(var(--media-spacing) * 1);
pointer-events: none;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-shadow-current-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail),
.media-slider__preview[data-pointing]:not([data-dragging])::before {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(0.5 * var(--media-internal-scale-unit));
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(--media-popup-translate-x-distance, 0), var(--media-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"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 3) 0;
border-radius: calc(Infinity * 1px);
&:has(media-volume-slider[data-hidden]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
font-size: var(--media-font-size-base);
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: calc(var(--media-spacing) * 1);
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: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-default-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 3);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--media-menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin-block: calc(var(--media-spacing) * 1);
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
opacity: 0;
.media-icon {
filter: drop-shadow(0 1px 0 var(--media-shadow-current-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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
.media-default-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: calc(var(--media-spacing) * -2);
&:has(.media-controls[data-visible]) {
--media-caption-track-y: calc(var(--media-spacing) * -14);
}
}
.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);
}
.media-default-skin .media-input-indicator-overlay {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
color: oklch(1 0 0);
pointer-events: none;
}
.media-default-skin {
.media-volume-indicator,
.media-status-indicator--state {
--media-surface-background-color: oklch(0 0 0 / 0.25);
position: absolute;
top: calc(var(--media-spacing) * 3);
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-volume-indicator__content,
.media-status-indicator__content {
display: flex;
gap: calc(var(--media-spacing) * 2);
align-items: center;
justify-content: space-between;
width: 100%;
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
* {
mix-blend-mode: difference;
}
}
.media-icon {
display: none;
flex-shrink: 0;
}
.media-volume-indicator__value,
.media-status-indicator__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);
}
&[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;
}
}
&[data-ending-style] {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -25%;
}
}
}
.media-seek-indicator,
.media-status-indicator--playback {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: calc(var(--media-spacing) * 4);
text-align: center;
}
}
.media-default-skin .media-volume-indicator {
width: min(80%, calc(var(--media-spacing) * 48));
transform: translateX(0);
.media-volume-indicator__content {
background-image: linear-gradient(currentColor, currentColor);
background-repeat: no-repeat;
background-position: left;
background-size: var(--media-volume-fill, 0%) 100%;
border-radius: inherit;
transition: background-size 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]:not([data-starting-style], [data-ending-style]),
&[data-max]:not([data-starting-style], [data-ending-style]) {
transform: translateX(0.25px);
transition: transform 300ms linear(0, -24 20%, 16 40%, -8 60%, 4 80%, 1);
}
}
}
.media-default-skin .media-status-indicator--state {
&[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;
}
}
.media-default-skin .media-status-indicator--playback {
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;
.media-icon {
grid-area: 1 / 1;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
opacity: 0;
scale: 0;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
&.media-icon--play {
translate: 1px 0;
}
}
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
opacity: 1;
scale: 1;
}
&[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;
&[data-starting-style],
&[data-ending-style],
.media-icon {
scale: 1;
}
.media-icon {
transition-duration: 50ms;
transition-property: opacity;
}
}
}
.media-default-skin .media-seek-indicator {
gap: calc(var(--media-spacing) * 1);
.media-seek-indicator__value {
font-variant-numeric: tabular-nums;
}
@container media-root (width > 24rem) {
padding: calc(var(--media-spacing) * 6);
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
.media-icon--seek {
display: block;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[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 shared
by the HTML and React skins.
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon,
.media-button--mute .media-icon,
.media-button--fullscreen .media-icon,
.media-button--pip .media-icon,
.media-button--cast .media-icon,
.media-button--airplay .media-icon,
.media-button--captions .media-icon {
opacity: 0;
}
.media-button--play .media-icon {
scale: 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 {
opacity: 1;
scale: 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;
}
@media (prefers-reduced-motion: reduce) {
.media-button--airplay {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
}
/* Video-specific container styles */
.media-default-skin--video {
--media-default-accent-color: oklch(1 0 0);
--media-border-color: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.15));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-video-border-radius: var(--media-container-border-radius);
--media-surface-background-color: oklch(1 0 0 / 0.1);
--media-surface-inner-border-color: oklch(1 0 0 / 0.1);
--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-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: ease-out;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
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-error__dialog {
scale: 1;
transition-property: opacity;
}
}
@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--root: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-container-border-radius: 0;
&::after {
display: none;
}
@media (width >= 1280px) {
--media-scale: 1.25;
}
@media (width >= 1536px) {
--media-scale: 1.5;
}
@media (width >= 1920px) {
--media-scale: 1.75;
}
}
* {
--media-focus-ring-color: oklch(1 0 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: calc(var(--media-spacing) * 3);
width: 100%;
max-width: calc(var(--media-spacing) * 72);
padding: calc(var(--media-spacing) * 3);
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.25);
border-radius: calc(var(--media-spacing) * 7);
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.95;
}
.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: calc(var(--media-spacing) * 2);
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 1.5);
text-shadow: inherit;
}
.media-default-skin--video .media-error__title {
font-size: var(--media-font-size-medium);
}
.media-default-skin--video .media-slider__value {
text-shadow: 0 1px 0 var(--media-shadow-current-color);
}
/* Controls (hide/show behavior) */
.media-default-skin--video .media-controls--root {
--media-inset-factor: 2;
--media-inset: calc(var(--media-spacing) * var(--media-inset-factor));
--media-base-boundary-offset: var(--media-inset-factor);
z-index: 10;
display: contents;
color: oklch(1 0 0);
transition-timing-function: var(--media-controls-transition-timing-function);
/* Speed up the entry transition */
transition-duration: calc(var(--media-controls-transition-duration) / 2);
.media-controls--primary,
.media-controls--secondary {
position: absolute;
}
/* Primary must be higher for the preview/thumbnail */
.media-controls--primary {
z-index: 20;
}
.media-controls--secondary {
z-index: 10;
}
.media-controls--primary {
inset-inline: var(--media-inset);
bottom: var(--media-inset);
transform-origin: bottom;
}
.media-controls--secondary {
top: var(--media-inset);
right: var(--media-inset);
container-type: normal;
transform-origin: top;
}
.media-time-controls {
flex: 1;
padding-inline: calc(var(--media-spacing) * 2);
}
@container media-root (width < 32rem) {
.media-controls--primary,
.media-controls--secondary {
transition-timing-function: inherit;
transition-duration: inherit;
@media (pointer: fine) {
transition-property: filter, opacity, scale, translate;
}
@media (pointer: coarse) {
transition-property: opacity, scale, translate;
}
}
&:after {
display: none;
}
&:not([data-visible]) {
.media-controls--primary,
.media-controls--secondary {
pointer-events: none;
opacity: 0;
scale: 0.95;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
}
.media-controls--primary {
@media (prefers-reduced-motion: no-preference) {
translate: 0 4px;
}
}
.media-controls--secondary {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -4px;
}
}
}
.media-button--captions {
display: none;
}
}
@container media-root (width >= 32rem) {
position: absolute;
inset-inline: var(--media-inset);
bottom: var(--media-inset);
display: flex;
transform-origin: bottom;
.media-controls--primary,
.media-controls--secondary {
display: contents;
&::after {
display: none;
}
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
scale: 0.95;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 4px;
}
}
.media-time-controls {
padding-inline: calc(var(--media-spacing) * 3);
}
}
@media (pointer: fine) {
transition-property: filter, opacity, scale, translate;
}
@media (pointer: coarse) {
transition-property: opacity, scale, translate;
}
@container media-root (width > 42rem) {
--media-inset-factor: 3;
}
}
.media-default-skin--video:has(.media-error[data-open]) .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-default-skin--video:has(.media-controls--root:not([data-visible])) {
cursor: none;
}
/* Sliders */
.media-default-skin--video .media-slider__track {
background-color: oklch(1 0 0 / 0.2);
}
Default audio skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { playbackRateText } from '@videojs/core/i18n/text/menu';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { useTranslator, Container, usePlayer, BufferingIndicator, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, PlaybackRateRadioGroup as PlaybackRateRadioGroupComponent, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster } from '@videojs/react';
import { Audio } from '@videojs/react/audio';
import './player.css';
import { Player } from './player';
function PlaybackRateRadioGroup(): ReactNode {
const t = useTranslator();
return (
<PlaybackRateRadioGroupComponent
className="media-menu__group"
aria-label={t(playbackRateText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<span>{item.label}</span>
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
);
}
function PlaybackRateTrigger(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
return (
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<Menu.Trigger
disabled={state.disabled}
render={<PlaybackRateButton className="media-button--playback-rate" render={<Button />} />}
/>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
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>
<Container className={`media-default-skin media-default-skin--audio ${className ?? ''}`} {...rest}>
<Audio src={src} />
<ErrorDialog.Root>
<ErrorDialog.Backdrop className="media-overlay media-error-backdrop" />
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title"></ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--subtle"></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.Buffer className="media-slider__buffer" />
<TimeSlider.Fill className="media-slider__fill" />
</TimeSlider.Track>
<TimeSlider.Thumb className="media-slider__thumb" />
<TimeSlider.Preview overflow="visible" className="media-slider__preview">
<TimeSlider.Value
type="pointer"
className="media-surface media-tooltip 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.Popup className="media-surface media-popover media-menu media-menu--playback-rate">
<Menu.Content className="media-menu__content">
<PlaybackRateRadioGroup />
</Menu.Content>
</Menu.Popup>
</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 className="media-sr-only" />
</Container>
</Player>
);
}
// ================================================================
// 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 volumeUnavailable = usePlayer((s) => s.volumeAvailability !== 'available');
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 (volumeUnavailable) 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>
);
}
import { createPlayer } from '@videojs/react';
import { audioFeatures } from '@videojs/react/audio';
export const { Player } = createPlayer({ features: audioFeatures });
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-default-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 1.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius, 1.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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 1px 0 0 var(--media-surface-inner-border-color),
inset 0 0 0 1px oklch(from var(--media-surface-inner-border-color) l c h / calc(alpha * 0.5));
}
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-default-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-default-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * var(--media-base-boundary-offset, 2));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
border-radius: calc(Infinity * 1px);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: calc(var(--media-spacing) * 2.5);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row-reverse;
}
> .media-time:last-child {
@container media-time-controls (width < 16rem) {
display: none;
}
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
}
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
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, color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-default-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-default-skin .media-button {
scale: 1;
transition-property: background-color, color;
will-change: auto;
}
}
.media-default-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-badge {
padding: calc(var(--media-spacing) * 0.5) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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);
}
.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,
.media-default-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-default-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.media-default-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: var(--media-track-border-radius);
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: inherit;
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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;
scale: 0.8;
translate: -50% -50%;
&[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: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
opacity: 1;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (hover: hover) and (pointer: fine) {
&:hover {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
&::after {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 36;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: var(--media-max-size);
height: calc(var(--media-spacing) * 1);
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: 50%;
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 9));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 10.5));
display: flex;
flex-direction: column;
align-items: center;
}
.media-slider__chapter-title {
min-width: 0;
max-width: var(--media-max-size);
padding-inline: calc(var(--media-spacing) * 6);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
width: calc(var(--media-spacing) * 1);
height: calc(var(--media-spacing) * 1);
pointer-events: none;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-shadow-current-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail),
.media-slider__preview[data-pointing]:not([data-dragging])::before {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(0.5 * var(--media-internal-scale-unit));
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(--media-popup-translate-x-distance, 0), var(--media-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"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 3) 0;
border-radius: calc(Infinity * 1px);
&:has(media-volume-slider[data-hidden]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
font-size: var(--media-font-size-base);
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: calc(var(--media-spacing) * 1);
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: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-default-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 3);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--media-menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin-block: calc(var(--media-spacing) * 1);
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
opacity: 0;
.media-icon {
filter: drop-shadow(0 1px 0 var(--media-shadow-current-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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size 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 shared
by the HTML and React skins.
========================================================================== */
/* --- 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;
}
/* Container */
.media-default-skin--audio {
--media-default-accent-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-text-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-border-color: oklch(0 0 0 / 0.1);
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-surface-background-color: light-dark(oklch(1 0 0 / 0.5), oklch(0 0 0 / 0.4));
--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 (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-surface-inner-border-color: light-dark(oklch(1 0 0 / 0.1), oklch(1 0 0 / 0.2));
--media-surface-outer-border-color: light-dark(oklch(0 0 0 / 0.05), transparent);
}
}
/* Error Dialog */
.media-default-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: calc(var(--media-spacing) * 3);
align-items: center;
padding-inline: calc(var(--media-spacing) * 5) calc(var(--media-spacing) * 0.5);
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: calc(var(--media-spacing) * 2);
align-items: center;
}
/* Controls */
.media-default-skin--audio .media-controls {
--media-base-boundary-offset: 2;
color: var(--media-text-color);
.media-time-controls {
padding-inline: calc(var(--media-spacing) * 3);
}
.media-button--seek {
@container media-root (width < 32rem) {
display: none;
}
}
}
/* 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: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.2));
box-shadow: 0 0 0 1px light-dark(transparent, oklch(0 0 0 / 0.05));
}
.media-default-skin--audio .media-slider .media-slider__preview .media-slider__value {
bottom: calc(var(--media-spacing) * 10);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/audio.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>
<media-dialog-backdrop class="media-overlay media-error-backdrop"></media-dialog-backdrop>
<media-dialog-popup class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-dialog-title class="media-error__title"></media-dialog-title>
<media-dialog-description class="media-error__description"></media-dialog-description>
</div>
<div class="media-error__actions">
<media-dialog-close class="media-button media-button--subtle"></media-dialog-close>
</div>
</div>
</media-dialog-popup>
</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-buffer class="media-slider__buffer"></media-slider-buffer>
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb"></media-slider-thumb>
<media-slider-preview overflow="visible" class="media-slider__preview">
<media-slider-value type="pointer" class="media-surface media-tooltip 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 id="playback-rate-trigger" 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-menu-content class="media-menu__content">
<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-content>
</media-menu>
<media-tooltip trigger="playback-rate-trigger" 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-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,
media-i18n,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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.
*/
media-container video,
media-container [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
media-container 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,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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 the volume popover when its slider is unavailable. */
.media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-default-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 1.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius, 1.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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 1px 0 0 var(--media-surface-inner-border-color),
inset 0 0 0 1px oklch(from var(--media-surface-inner-border-color) l c h / calc(alpha * 0.5));
}
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-default-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-default-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * var(--media-base-boundary-offset, 2));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
border-radius: calc(Infinity * 1px);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: calc(var(--media-spacing) * 2.5);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row-reverse;
}
> .media-time:last-child {
@container media-time-controls (width < 16rem) {
display: none;
}
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
}
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
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, color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-default-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-default-skin .media-button {
scale: 1;
transition-property: background-color, color;
will-change: auto;
}
}
.media-default-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-badge {
padding: calc(var(--media-spacing) * 0.5) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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);
}
.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,
.media-default-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-default-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.media-default-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: var(--media-track-border-radius);
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: inherit;
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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;
scale: 0.8;
translate: -50% -50%;
&[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: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
opacity: 1;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (hover: hover) and (pointer: fine) {
&:hover {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
&::after {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 36;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: var(--media-max-size);
height: calc(var(--media-spacing) * 1);
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: 50%;
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 9));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 10.5));
display: flex;
flex-direction: column;
align-items: center;
}
.media-slider__chapter-title {
min-width: 0;
max-width: var(--media-max-size);
padding-inline: calc(var(--media-spacing) * 6);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
width: calc(var(--media-spacing) * 1);
height: calc(var(--media-spacing) * 1);
pointer-events: none;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-shadow-current-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail),
.media-slider__preview[data-pointing]:not([data-dragging])::before {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(0.5 * var(--media-internal-scale-unit));
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(--media-popup-translate-x-distance, 0), var(--media-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"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 3) 0;
border-radius: calc(Infinity * 1px);
&:has(media-volume-slider[data-hidden]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
font-size: var(--media-font-size-base);
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: calc(var(--media-spacing) * 1);
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: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-default-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 3);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--media-menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin-block: calc(var(--media-spacing) * 1);
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
opacity: 0;
.media-icon {
filter: drop-shadow(0 1px 0 var(--media-shadow-current-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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size 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 shared
by the HTML and React skins.
========================================================================== */
/* --- 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;
}
/* Container */
.media-default-skin--audio {
--media-default-accent-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-text-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-border-color: oklch(0 0 0 / 0.1);
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-surface-background-color: light-dark(oklch(1 0 0 / 0.5), oklch(0 0 0 / 0.4));
--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 (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-surface-inner-border-color: light-dark(oklch(1 0 0 / 0.1), oklch(1 0 0 / 0.2));
--media-surface-outer-border-color: light-dark(oklch(0 0 0 / 0.05), transparent);
}
}
/* Error Dialog */
.media-default-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: calc(var(--media-spacing) * 3);
align-items: center;
padding-inline: calc(var(--media-spacing) * 5) calc(var(--media-spacing) * 0.5);
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: calc(var(--media-spacing) * 2);
align-items: center;
}
/* Controls */
.media-default-skin--audio .media-controls {
--media-base-boundary-offset: 2;
color: var(--media-text-color);
.media-time-controls {
padding-inline: calc(var(--media-spacing) * 3);
}
.media-button--seek {
@container media-root (width < 32rem) {
display: none;
}
}
}
/* 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: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.2));
box-shadow: 0 0 0 1px light-dark(transparent, oklch(0 0 0 / 0.05));
}
.media-default-skin--audio .media-slider .media-slider__preview .media-slider__value {
bottom: calc(var(--media-spacing) * 10);
}
Minimal video skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { audioText, captionsText, playbackRateText, qualityText, settingsText, speedText } from '@videojs/core/i18n/text/menu';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, QualityIcon, RestartIcon, SpeechIcon, SpeedIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { Poster, useTranslator, Container, usePlayer, AirPlayButton, useAudioTrackOptions, AudioTrackRadioGroup, BufferingIndicator, CaptionsButton, CaptionsRadioGroup, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, PlaybackRateRadioGroup, Popover, useQualityOptions, QualityRadioGroup, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './player.css';
import { Player } from './player';
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 t = useTranslator();
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">
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<Menu.Trigger aria-label={t(settingsText)} className="media-button--settings" render={<Button />}>
<GearIcon className="media-icon media-icon--settings" />
</Menu.Trigger>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label>{t(settingsText)}</Tooltip.Label>
</Tooltip.Popup>
</Tooltip.Root>
<Menu.Popup className="media-popover media-menu media-menu--settings">
<Menu.Content className="media-menu__content">
{hasQuality ? (
<Menu.Root>
<Menu.Trigger
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<QualityIcon className="media-icon" />
<span>{t(qualityText)}</span>
<span className="media-menu__hint">
<bdi dir="auto" className="media-menu__hint-label">
{quality.selectedLabel}
</bdi>
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Item className="media-menu__back">
<MenuChevron flipped />
{t(qualityText)}
</Menu.Item>
<Menu.Separator className="media-menu__separator" />
<QualityRadioGroup
className="media-menu__group"
aria-label={t(qualityText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<bdi dir="auto">
{item.label}
{item.tier ? <sup className="media-menu__tier">{item.tier}</sup> : null}
</bdi>
{item.badge ? <span className="media-badge">{item.badge}</span> : null}
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
</Menu.Content>
</Menu.Root>
) : null}
{hasAudioTrack ? (
<Menu.Root>
<Menu.Trigger
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<SpeechIcon className="media-icon" />
<span>{t(audioText)}</span>
<span className="media-menu__hint">
<bdi dir="auto" className="media-menu__hint-label">
{audioTrack.selectedLabel}
</bdi>
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Item className="media-menu__back">
<MenuChevron flipped />
{t(audioText)}
</Menu.Item>
<Menu.Separator className="media-menu__separator" />
<AudioTrackRadioGroup
className="media-menu__group"
aria-label={t(audioText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<bdi dir="auto">{item.label}</bdi>
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
</Menu.Content>
</Menu.Root>
) : null}
{hasPlaybackRate ? (
<Menu.Root>
<Menu.Trigger
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<SpeedIcon className="media-icon" />
<span>{t(speedText)}</span>
<span className="media-menu__hint">
<bdi dir="auto" className="media-menu__hint-label">
{playbackRate.selectedLabel}
</bdi>
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Item className="media-menu__back">
<MenuChevron flipped />
{t(speedText)}
</Menu.Item>
<Menu.Separator className="media-menu__separator" />
<PlaybackRateRadioGroup
className="media-menu__group"
aria-label={t(playbackRateText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<bdi dir="auto">{item.label}</bdi>
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
</Menu.Content>
</Menu.Root>
) : null}
{hasCaptions ? (
<Menu.Root>
<Menu.Trigger
className="media-menu__item media-menu__item--submenu"
render={(props) => (
<div {...props}>
<CaptionsOffIcon className="media-icon" />
<span>{t(captionsText)}</span>
<span className="media-menu__hint">
<bdi dir="auto" className="media-menu__hint-label">
{captions.selectedLabel}
</bdi>
<MenuChevron />
</span>
</div>
)}
/>
<Menu.Content className="media-menu__panel">
<Menu.Item className="media-menu__back">
<MenuChevron flipped />
{t(captionsText)}
</Menu.Item>
<Menu.Separator className="media-menu__separator" />
<CaptionsRadioGroup
className="media-menu__group"
aria-label={t(captionsText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<bdi dir="auto">{item.label}</bdi>
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
</Menu.Content>
</Menu.Root>
) : null}
</Menu.Content>
</Menu.Popup>
</Menu.Root>
);
}
// ================================================================
// Player
// ================================================================
const SEEK_TIME = 10;
export interface VideoPlayerProps {
src: string;
poster?: string | undefined;
style?: CSSProperties;
className?: string;
renderPoster?: Poster.Props['render'];
}
/**
* @example
* ```tsx
* <VideoPlayer
* src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
* poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp"
* />
* ```
*/
export function VideoPlayer({ src, poster, className, renderPoster, style, ...rest }: VideoPlayerProps): ReactNode {
return (
<Player poster={poster}>
<Container className={`media-minimal-skin media-minimal-skin--video ${className ?? ''}`} style={style} {...rest}>
<Video src={src} playsInline />
<Poster render={renderPoster} />
<BufferingIndicator
render={(props) => (
<div {...props} className="media-buffering-indicator">
<SpinnerIcon className="media-icon" />
</div>
)}
/>
<ErrorDialog.Root>
<ErrorDialog.Backdrop className="media-overlay media-error-backdrop" />
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title"></ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--primary"></ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root>
<Controls.Backdrop className="media-overlay" />
<Controls.Content 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>
<VolumePopover />
</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.Chapters
className="media-slider__chapters"
renderChapter={(props) => (
<div {...props} className={`${props.className} media-slider__chapter`}>
<TimeSlider.Track className="media-slider__track media-slider__chapter-track">
<TimeSlider.Buffer className="media-slider__buffer" />
<TimeSlider.Fill className="media-slider__fill" />
</TimeSlider.Track>
</div>
)}
/>
<TimeSlider.Thumb className="media-slider__thumb" />
<TimeSlider.Preview className="media-slider__preview">
<div className="media-thumbnail media-slider__thumbnail">
<Slider.Thumbnail className="media-thumbnail__image" />
<SpinnerIcon className="media-thumbnail__spinner media-icon" />
</div>
<div className="media-slider__value">
<TimeSlider.ChapterTitle className="media-slider__chapter-title" />
<TimeSlider.Value type="pointer" className="media-time" />
</div>
</TimeSlider.Preview>
</TimeSlider.Root>
</div>
<div className="media-button-group">
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CaptionsButton className="media-button--captions" render={<Button />}>
<CaptionsOffIcon className="media-icon media-icon--captions-off" />
<CaptionsOnIcon className="media-icon media-icon--captions-on" />
</CaptionsButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<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.Content>
</Controls.Root>
{/* 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 Indicators */}
<StatusAnnouncer className="media-sr-only" />
<div className="media-input-indicator-overlay">
<VolumeIndicator.Root className="media-volume-indicator">
<VolumeIndicator.Fill className="media-volume-indicator__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-volume-indicator__progress" aria-hidden="true" />
<VolumeIndicator.Value className="media-volume-indicator__value" />
</VolumeIndicator.Fill>
</VolumeIndicator.Root>
<StatusIndicator.Root
actions={TOP_STATUS_ACTIONS}
className="media-status-indicator media-status-indicator--state"
>
<div className="media-status-indicator__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-status-indicator__value" />
</div>
</StatusIndicator.Root>
<SeekIndicator.Root className="media-seek-indicator">
<ChevronIcon className="media-icon media-icon--seek" />
<SeekIndicator.Value className="media-seek-indicator__value" />
</SeekIndicator.Root>
<StatusIndicator.Root
actions={CENTER_STATUS_ACTIONS}
className="media-status-indicator media-status-indicator--playback"
>
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</StatusIndicator.Root>
</div>
</Container>
</Player>
);
}
// ================================================================
// 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 volumeUnavailable = usePlayer((s) => s.volumeAvailability !== 'available');
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 (volumeUnavailable) {
return (
<Tooltip.Root side="top" delay={0} sticky>
<Tooltip.Trigger render={muteButton} />
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="right">
<Tooltip.Root side="top" delay={0} sticky>
<Tooltip.Trigger render={<Popover.Trigger render={muteButton} />} />
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<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>
);
}
import { createPlayer } from '@videojs/react';
import { videoFeatures } from '@videojs/react/video';
export const { Player } = createPlayer({ features: videoFeatures });
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-minimal-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 0.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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-container-border-radius);
}
.media-minimal-skin video {
border-radius: inherit;
}
.media-minimal-skin:fullscreen ::slotted(video),
.media-minimal-skin:fullscreen video {
object-fit: contain;
}
.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) calc(var(--media-spacing) * 30),
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-backdrop {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-minimal-skin .media-controls-backdrop[data-visible],
.media-minimal-skin .media-error-backdrop[data-open] {
opacity: 1;
}
.media-minimal-skin:has(.media-buffering-indicator[data-visible]) .media-controls-backdrop {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-minimal-skin .media-error-backdrop[data-open] {
backdrop-filter: blur(16px) saturate(1.2);
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-minimal-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-minimal-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * (var(--media-base-boundary-offset, 0) + 1));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 3);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row;
}
}
.media-minimal-skin .media-time-group {
display: flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
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;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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;
}
}
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9.5);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(var(--media-spacing) * 2);
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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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-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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-minimal-skin .media-button {
scale: 1;
transition-property: background-color;
will-change: auto;
}
}
.media-minimal-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-badge {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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(var(--media-spacing) * 1);
}
.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,
.media-minimal-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-minimal-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.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]) {
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-container-border-radius);
}
.media-minimal-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
.media-minimal-skin:fullscreen media-poster ::slotted(img),
.media-minimal-skin:fullscreen media-poster img,
.media-minimal-skin:fullscreen > img {
object-fit: contain;
}
.media-minimal-skin .media-thumbnail {
position: relative;
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: calc(var(--media-spacing) * 2);
&::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 {
position: relative;
display: block;
max-width: var(--media-thumbnail-max-width);
max-height: var(--media-thumbnail-max-height);
overflow: clip;
border-radius: inherit;
}
.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;
}
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
&:has(.media-thumbnail__image[data-loading]) {
width: var(--media-thumbnail-max-width);
max-width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
.media-thumbnail__image {
opacity: 0;
}
.media-thumbnail__spinner {
opacity: 1;
}
}
}
.media-minimal-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: var(--media-track-border-radius);
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.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: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: var(--media-track-border-radius);
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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%;
&[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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:focus-visible,
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 28;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: 100%;
height: calc(var(--media-spacing) * 1);
@container media-root (width > 32rem) {
--media-max-size-factor: 36;
}
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: var(--media-preview-left, var(--media-slider-pointer));
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: filter, opacity, scale;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 11));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 5));
display: flex;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 2);
justify-content: center;
}
.media-slider__chapter-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
z-index: 1;
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
&[data-orientation="horizontal"]::before {
top: 50%;
left: var(--media-slider-pointer);
width: 1px;
height: calc(var(--media-spacing) * 5);
}
&[data-orientation="vertical"]::before {
top: calc(100% - var(--media-slider-pointer));
left: 50%;
width: calc(var(--media-spacing) * 5);
height: 1px;
}
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail) {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(var(--media-spacing) * 2);
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--media-popup-translate-x-distance, 0), var(--media-popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
filter: blur(4px);
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2);
font-size: var(--media-font-size-base);
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 1);
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: calc(var(--media-spacing) * -1);
font-family: inherit;
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-minimal-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 2.5);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--media-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) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin: calc(var(--media-spacing) * 1) 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
.media-minimal-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: calc(var(--media-spacing) * -2);
&:has(.media-controls[data-visible]) {
--media-caption-track-y: calc(var(--media-spacing) * -18);
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
--media-caption-track-y: calc(var(--media-spacing) * -12);
}
}
}
.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);
}
.media-minimal-skin .media-input-indicator-overlay {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
overflow: hidden;
color: oklch(1 0 0);
pointer-events: none;
border-radius: inherit;
}
.media-minimal-skin {
.media-volume-indicator,
.media-status-indicator--state {
position: absolute;
inset-inline: 0;
top: 0;
display: flex;
justify-content: center;
padding-top: calc(var(--media-spacing) * 3);
padding-bottom: calc(var(--media-spacing) * 32);
color: inherit;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
pointer-events: none;
background-image: linear-gradient(
to bottom,
oklch(0 0 0 / 0.35),
oklch(0 0 0 / 0.2) calc(var(--media-spacing) * 12),
oklch(0 0 0 / 0)
);
transform-origin: top center;
transition-timing-function: ease-out;
transition-duration: 100ms;
.media-volume-indicator__content,
.media-status-indicator__content {
display: flex;
gap: calc(var(--media-spacing) * 2);
align-items: center;
justify-content: space-between;
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
}
.media-icon {
display: none;
flex-shrink: 0;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
.media-volume-indicator__value,
.media-status-indicator__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-volume-indicator__content,
.media-status-indicator__content {
background: var(--media-controls-background-color);
border-radius: calc(var(--media-spacing) * 2);
}
}
&[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);
}
}
&[data-ending-style] {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -100%;
}
}
}
.media-seek-indicator,
.media-status-indicator--playback {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: calc(var(--media-spacing) * 4);
text-align: center;
}
}
.media-minimal-skin .media-volume-indicator {
.media-volume-indicator__content {
width: min(80%, calc(var(--media-spacing) * 56));
transform: translateX(0);
}
.media-volume-indicator__progress {
position: relative;
width: 100%;
height: calc(var(--media-spacing) * 0.75);
background: oklch(from currentColor l c h / 0.2);
border-radius: calc(Infinity * 1px);
box-shadow: 0 1px 0 var(--media-shadow-subtle-current-color);
&::before {
position: absolute;
inset-block: 0;
left: 0;
width: var(--media-volume-fill, 0%);
content: "";
background: var(--media-internal-accent-color);
border-radius: inherit;
transition: width 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]:not([data-starting-style], [data-ending-style]) .media-volume-indicator__content,
&[data-max]:not([data-starting-style], [data-ending-style]) .media-volume-indicator__content {
transform: translateX(0.25px);
transition: transform 300ms linear(0, -24 20%, 16 40%, -8 60%, 4 80%, 1);
}
}
}
.media-minimal-skin .media-status-indicator--state {
&[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;
}
}
.media-minimal-skin .media-status-indicator--playback {
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
.media-icon {
grid-area: 1 / 1;
width: calc(var(--media-icon-size) * 2);
height: calc(var(--media-icon-size) * 2);
opacity: 0;
scale: 0;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
opacity: 1;
scale: 1;
}
&[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;
&[data-starting-style],
&[data-ending-style],
.media-icon {
scale: 1;
}
.media-icon {
transition-duration: 50ms;
transition-property: opacity;
}
}
}
.media-minimal-skin .media-seek-indicator {
gap: calc(var(--media-spacing) * 1);
.media-seek-indicator__value {
font-variant-numeric: tabular-nums;
}
@container media-root (width > 24rem) {
padding: calc(var(--media-spacing) * 6);
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
.media-icon--seek {
display: block;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[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 shared
by the HTML and React skins.
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon,
.media-button--mute .media-icon,
.media-button--fullscreen .media-icon,
.media-button--pip .media-icon,
.media-button--cast .media-icon,
.media-button--airplay .media-icon,
.media-button--captions .media-icon {
opacity: 0;
}
.media-button--play .media-icon {
scale: 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 {
opacity: 1;
scale: 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;
}
@media (prefers-reduced-motion: reduce) {
.media-button--airplay {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
}
/* Container */
.media-minimal-skin--video {
--media-default-accent-color: oklch(1 0 0);
--media-border-color: light-dark(oklch(0 0 0 / 0.15), oklch(1 0 0 / 0.15));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-video-border-radius: var(--media-container-border-radius);
--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-popover-backdrop-filter: blur(16px) saturate(1.5);
--media-popover-background-color: oklch(0 0 0 / 0.5);
--media-popover-border-color: oklch(1 0 0 / 0.1);
--media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
--media-tooltip-background-color: var(--media-popover-background-color);
--media-tooltip-border-color: var(--media-popover-border-color);
--media-tooltip-text-color: currentColor;
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-error__dialog {
scale: 1;
transition-property: opacity;
}
}
@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);
}
&: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-container-border-radius: 0;
&::after {
display: none;
}
@media (width >= 1280px) {
--media-scale: 1.25;
}
@media (width >= 1536px) {
--media-scale: 1.5;
}
@media (width >= 1920px) {
--media-scale: 1.75;
}
}
* {
--media-focus-ring-color: oklch(1 0 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: calc(var(--media-spacing) * 3);
width: 100%;
max-width: calc(var(--media-spacing) * 64);
padding: calc(var(--media-spacing) * 4);
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__content {
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 2);
padding: calc(var(--media-spacing) * 1.5) 0;
}
.media-minimal-skin--video .media-error__title {
font-size: var(--media-font-size-medium);
}
.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.95;
}
.media-minimal-skin--video .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
/* Controls (hide/show behavior) */
.media-minimal-skin--video .media-controls {
--media-base-side-offset: 5;
--media-base-boundary-offset: 1;
--media-inset-factor: 1;
--media-inset: calc(var(--media-spacing) * var(--media-inset-factor));
--media-volume-mask: linear-gradient(to right, transparent 10%, #000 25%, #000 100%);
position: absolute;
inset-inline: var(--media-inset);
bottom: var(--media-inset);
z-index: 10;
flex-wrap: wrap;
column-gap: calc(var(--media-spacing) * 2);
color: oklch(1 0 0);
border-radius: calc(var(--media-spacing) * 3);
transition-timing-function: var(--media-controls-transition-timing-function);
/* Speed up the entry transition */
transition-duration: calc(var(--media-controls-transition-duration) / 2);
@media (pointer: fine) {
transition-property: translate, filter, opacity;
}
@media (pointer: coarse) {
transition-property: translate, opacity;
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 100%;
}
}
.media-time-controls {
--media-slider-height: calc(var(--media-spacing) * 5);
flex: 0 0 100%;
order: -1;
padding-inline: calc(var(--media-spacing) * 1.5);
}
.media-button-group:first-child {
flex: 1;
text-align: left;
}
.media-button-group:last-child {
flex: 1;
justify-content: end;
}
:is(.media-time-controls, .media-button-group:last-child) {
mask-image: var(--media-volume-mask-image, none);
mask-repeat: no-repeat;
mask-position: var(--media-volume-mask-position, 100% 0);
mask-size: var(--media-volume-mask-size, 200% 100%);
transition: mask-position 50ms ease-out;
}
&:has(.media-button--mute[aria-expanded="true"]) {
@container media-root (width <= 42rem) {
.media-button-group:last-child {
--media-volume-mask-image: var(--media-volume-mask);
--media-volume-mask-position: 0 0;
--media-volume-mask-size: 400% 100%;
}
}
@container media-root (width > 42rem) {
.media-time-controls {
--media-volume-mask-image: var(--media-volume-mask);
--media-volume-mask-position: 0 0;
}
}
}
@container media-root (width > 42rem) {
--media-inset-factor: 2;
--media-base-side-offset: 2;
flex-wrap: nowrap;
.media-time-controls {
--media-slider-height: calc(var(--media-spacing) * 8);
flex: 1;
order: unset;
}
.media-button-group:first-child,
.media-button-group:last-child {
flex: 0 0 auto;
}
}
}
.media-minimal-skin--video:has(.media-error[data-open]) .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-minimal-skin--video:has(.media-controls:not([data-visible])) {
cursor: none;
}
/* Popups & Animations */
.media-minimal-skin--video .media-popover--volume {
--media-popover-side-offset: 0rem;
padding: 0 calc(var(--media-spacing) * 3);
background: transparent;
}
/* Slider preview */
.media-minimal-skin--video .media-slider__value {
padding-inline: calc(var(--media-spacing) * 3);
text-shadow: 0 1px 0 var(--media-shadow-current-color);
}
.media-minimal-skin--video .media-slider .media-slider__preview {
--media-preview-end-inset: calc(100cqi - 100%);
--media-preview-left: clamp(
calc(var(--media-max-size) / 2),
var(--media-slider-pointer),
calc(100% - var(--media-max-size) / 2 + var(--media-preview-end-inset))
);
@container media-root (width > 42rem) {
--media-preview-left: var(--media-slider-pointer);
}
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/video-minimal.js"></script>
<link rel="stylesheet" href="./player.css">
<video-player poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp">
<media-container class="media-minimal-skin media-minimal-skin--video">
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" playsinline></video>
<media-poster>
<img alt="" decoding="async" />
</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>
<media-dialog-backdrop class="media-overlay media-error-backdrop"></media-dialog-backdrop>
<media-dialog-popup class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-dialog-title class="media-error__title"></media-dialog-title>
<media-dialog-description class="media-error__description"></media-dialog-description>
</div>
<div class="media-error__actions">
<media-dialog-close class="media-button media-button--primary"></media-dialog-close>
</div>
</div>
</media-dialog-popup>
</media-error-dialog>
<media-controls>
<media-controls-backdrop class="media-overlay"></media-controls-backdrop>
<media-controls-content 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-mute-button id="video-mute-trigger" 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-tooltip trigger="video-mute-trigger" delay="0" sticky side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-popover id="video-volume-popover" open-on-hover delay="200" close-delay="100" side="right" 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>
<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-time-slider-chapters class="media-slider__chapters">
<template>
<div class="media-slider__chapter">
<media-slider-track class="media-slider__track media-slider__chapter-track">
<media-slider-buffer class="media-slider__buffer"></media-slider-buffer>
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
</div>
</template>
</media-time-slider-chapters>
<media-slider-thumb class="media-slider__thumb"></media-slider-thumb>
<media-slider-preview class="media-slider__preview">
<div class="media-thumbnail media-slider__thumbnail">
<media-slider-thumbnail class="media-thumbnail__image"></media-slider-thumbnail>
<media-icon name="spinner" family="minimal" class="media-thumbnail__spinner media-icon"></media-icon>
</div>
<div class="media-slider__value">
<media-time-slider-chapter-title class="media-slider__chapter-title"></media-time-slider-chapter-title>
<media-slider-value type="pointer" class="media-time"></media-slider-value>
</div>
</media-slider-preview>
</media-time-slider>
</div>
<div class="media-button-group">
<media-captions-button commandfor="captions-tooltip" class="media-button media-button--subtle media-button--icon media-button--captions">
<media-icon name="captions-off" family="minimal" class="media-icon media-icon--captions-off"></media-icon>
<media-icon name="captions-on" family="minimal" class="media-icon media-icon--captions-on"></media-icon>
</media-captions-button>
<media-tooltip id="captions-tooltip" side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<button id="settings-trigger" commandfor="settings-menu" aria-labelledby="settings-label" 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>
<media-text token="menu.settings" id="settings-label" class="media-sr-only">Settings</media-text>
</button>
<media-menu id="settings-menu" side="top" align="center" class="media-popover media-menu media-menu--settings">
<media-menu-content class="media-menu__content">
<media-menu-item commandfor="settings-quality-menu" class="media-menu__item media-menu__item--submenu">
<media-icon name="switches" family="minimal" class="media-icon"></media-icon>
<media-text token="menu.quality">Quality</media-text>
<span class="media-menu__hint">
<bdi data-part="hint" dir="auto" class="media-menu__hint-label"></bdi>
<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" class="media-menu__item media-menu__item--submenu">
<media-icon name="speech" family="minimal" class="media-icon"></media-icon>
<media-text token="menu.audio">Audio</media-text>
<span class="media-menu__hint">
<bdi data-part="hint" dir="auto" class="media-menu__hint-label"></bdi>
<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" class="media-menu__item media-menu__item--submenu">
<media-icon name="speed" family="minimal" class="media-icon"></media-icon>
<media-text token="menu.speed">Speed</media-text>
<span class="media-menu__hint">
<bdi data-part="hint" dir="auto" class="media-menu__hint-label"></bdi>
<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" class="media-menu__item media-menu__item--submenu">
<media-icon name="captions-off" family="minimal" class="media-icon"></media-icon>
<media-text token="menu.captions">Captions</media-text>
<span class="media-menu__hint">
<bdi data-part="hint" dir="auto" class="media-menu__hint-label"></bdi>
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron"></media-icon>
</span>
</media-menu-item>
</media-menu-content>
<media-menu-content id="settings-quality-menu" class="media-menu__panel">
<media-menu-item class="media-menu__back">
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
<media-text token="menu.quality">Quality</media-text>
</media-menu-item>
<div class="media-menu__separator"></div>
<media-quality-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<span>
<bdi data-part="label" dir="auto"></bdi>
<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-content>
<media-menu-content id="settings-audio-menu" class="media-menu__panel">
<media-menu-item class="media-menu__back">
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
<media-text token="menu.audio">Audio</media-text>
</media-menu-item>
<div class="media-menu__separator"></div>
<media-audio-track-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<bdi data-part="label" dir="auto"></bdi>
<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-content>
<media-menu-content id="settings-speed-menu" class="media-menu__panel">
<media-menu-item class="media-menu__back">
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
<media-text token="menu.speed">Speed</media-text>
</media-menu-item>
<div class="media-menu__separator"></div>
<media-playback-rate-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<bdi data-part="label" dir="auto"></bdi>
<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-content>
<media-menu-content id="settings-captions-menu" class="media-menu__panel">
<media-menu-item class="media-menu__back">
<media-icon name="chevron" family="minimal" class="media-icon media-menu__chevron media-icon--flipped"></media-icon>
<media-text token="menu.captions">Captions</media-text>
</media-menu-item>
<div class="media-menu__separator"></div>
<media-captions-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<bdi data-part="label" dir="auto"></bdi>
<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-content>
</media-menu>
<media-tooltip id="settings-tooltip" trigger="settings-trigger" side="top" class="media-tooltip">
<media-text token="menu.settings">Settings</media-text>
</media-tooltip>
<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-content>
</media-controls>
<!-- 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 Indicators -->
<media-status-announcer class="media-sr-only"></media-status-announcer>
<div class="media-input-indicator-overlay">
<media-volume-indicator hidden class="media-volume-indicator">
<media-volume-indicator-fill class="media-volume-indicator__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-volume-indicator__progress" aria-hidden="true"></div>
<media-volume-indicator-value class="media-volume-indicator__value"></media-volume-indicator-value>
</media-volume-indicator-fill>
</media-volume-indicator>
<media-status-indicator hidden actions="toggleSubtitles toggleFullscreen togglePictureInPicture" class="media-status-indicator media-status-indicator--state">
<div class="media-status-indicator__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-status-indicator__value"></media-status-indicator-value>
</div>
</media-status-indicator>
<media-seek-indicator hidden class="media-seek-indicator">
<media-icon name="chevron" family="minimal" class="media-icon media-icon--seek"></media-icon>
<media-seek-indicator-value class="media-seek-indicator__value"></media-seek-indicator-value>
</media-seek-indicator>
<media-status-indicator hidden actions="togglePaused" class="media-status-indicator media-status-indicator--playback">
<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,
media-i18n,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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.
*/
media-container video,
media-container [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
media-container 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,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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 the volume popover when its slider is unavailable. */
.media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-minimal-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 0.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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-container-border-radius);
}
.media-minimal-skin video {
border-radius: inherit;
}
.media-minimal-skin:fullscreen ::slotted(video),
.media-minimal-skin:fullscreen video {
object-fit: contain;
}
.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) calc(var(--media-spacing) * 30),
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-backdrop {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-minimal-skin .media-controls-backdrop[data-visible],
.media-minimal-skin .media-error-backdrop[data-open] {
opacity: 1;
}
.media-minimal-skin:has(.media-buffering-indicator[data-visible]) .media-controls-backdrop {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-minimal-skin .media-error-backdrop[data-open] {
backdrop-filter: blur(16px) saturate(1.2);
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-minimal-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-minimal-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * (var(--media-base-boundary-offset, 0) + 1));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 3);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row;
}
}
.media-minimal-skin .media-time-group {
display: flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
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;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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;
}
}
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9.5);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(var(--media-spacing) * 2);
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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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-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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-minimal-skin .media-button {
scale: 1;
transition-property: background-color;
will-change: auto;
}
}
.media-minimal-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-badge {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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(var(--media-spacing) * 1);
}
.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,
.media-minimal-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-minimal-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.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]) {
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-container-border-radius);
}
.media-minimal-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
.media-minimal-skin:fullscreen media-poster ::slotted(img),
.media-minimal-skin:fullscreen media-poster img,
.media-minimal-skin:fullscreen > img {
object-fit: contain;
}
.media-minimal-skin .media-thumbnail {
position: relative;
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: calc(var(--media-spacing) * 2);
&::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 {
position: relative;
display: block;
max-width: var(--media-thumbnail-max-width);
max-height: var(--media-thumbnail-max-height);
overflow: clip;
border-radius: inherit;
}
.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;
}
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
&:has(.media-thumbnail__image[data-loading]) {
width: var(--media-thumbnail-max-width);
max-width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
.media-thumbnail__image {
opacity: 0;
}
.media-thumbnail__spinner {
opacity: 1;
}
}
}
.media-minimal-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: var(--media-track-border-radius);
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.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: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: var(--media-track-border-radius);
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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%;
&[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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:focus-visible,
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 28;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: 100%;
height: calc(var(--media-spacing) * 1);
@container media-root (width > 32rem) {
--media-max-size-factor: 36;
}
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: var(--media-preview-left, var(--media-slider-pointer));
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: filter, opacity, scale;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 11));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 5));
display: flex;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 2);
justify-content: center;
}
.media-slider__chapter-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
z-index: 1;
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
&[data-orientation="horizontal"]::before {
top: 50%;
left: var(--media-slider-pointer);
width: 1px;
height: calc(var(--media-spacing) * 5);
}
&[data-orientation="vertical"]::before {
top: calc(100% - var(--media-slider-pointer));
left: 50%;
width: calc(var(--media-spacing) * 5);
height: 1px;
}
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail) {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(var(--media-spacing) * 2);
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--media-popup-translate-x-distance, 0), var(--media-popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
filter: blur(4px);
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2);
font-size: var(--media-font-size-base);
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 1);
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: calc(var(--media-spacing) * -1);
font-family: inherit;
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-minimal-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 2.5);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--media-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) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin: calc(var(--media-spacing) * 1) 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
.media-minimal-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: calc(var(--media-spacing) * -2);
&:has(.media-controls[data-visible]) {
--media-caption-track-y: calc(var(--media-spacing) * -18);
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
--media-caption-track-y: calc(var(--media-spacing) * -12);
}
}
}
.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);
}
.media-minimal-skin .media-input-indicator-overlay {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
overflow: hidden;
color: oklch(1 0 0);
pointer-events: none;
border-radius: inherit;
}
.media-minimal-skin {
.media-volume-indicator,
.media-status-indicator--state {
position: absolute;
inset-inline: 0;
top: 0;
display: flex;
justify-content: center;
padding-top: calc(var(--media-spacing) * 3);
padding-bottom: calc(var(--media-spacing) * 32);
color: inherit;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
pointer-events: none;
background-image: linear-gradient(
to bottom,
oklch(0 0 0 / 0.35),
oklch(0 0 0 / 0.2) calc(var(--media-spacing) * 12),
oklch(0 0 0 / 0)
);
transform-origin: top center;
transition-timing-function: ease-out;
transition-duration: 100ms;
.media-volume-indicator__content,
.media-status-indicator__content {
display: flex;
gap: calc(var(--media-spacing) * 2);
align-items: center;
justify-content: space-between;
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
}
.media-icon {
display: none;
flex-shrink: 0;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
.media-volume-indicator__value,
.media-status-indicator__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-volume-indicator__content,
.media-status-indicator__content {
background: var(--media-controls-background-color);
border-radius: calc(var(--media-spacing) * 2);
}
}
&[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);
}
}
&[data-ending-style] {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -100%;
}
}
}
.media-seek-indicator,
.media-status-indicator--playback {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: calc(var(--media-spacing) * 4);
text-align: center;
}
}
.media-minimal-skin .media-volume-indicator {
.media-volume-indicator__content {
width: min(80%, calc(var(--media-spacing) * 56));
transform: translateX(0);
}
.media-volume-indicator__progress {
position: relative;
width: 100%;
height: calc(var(--media-spacing) * 0.75);
background: oklch(from currentColor l c h / 0.2);
border-radius: calc(Infinity * 1px);
box-shadow: 0 1px 0 var(--media-shadow-subtle-current-color);
&::before {
position: absolute;
inset-block: 0;
left: 0;
width: var(--media-volume-fill, 0%);
content: "";
background: var(--media-internal-accent-color);
border-radius: inherit;
transition: width 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]:not([data-starting-style], [data-ending-style]) .media-volume-indicator__content,
&[data-max]:not([data-starting-style], [data-ending-style]) .media-volume-indicator__content {
transform: translateX(0.25px);
transition: transform 300ms linear(0, -24 20%, 16 40%, -8 60%, 4 80%, 1);
}
}
}
.media-minimal-skin .media-status-indicator--state {
&[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;
}
}
.media-minimal-skin .media-status-indicator--playback {
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
.media-icon {
grid-area: 1 / 1;
width: calc(var(--media-icon-size) * 2);
height: calc(var(--media-icon-size) * 2);
opacity: 0;
scale: 0;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
opacity: 1;
scale: 1;
}
&[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;
&[data-starting-style],
&[data-ending-style],
.media-icon {
scale: 1;
}
.media-icon {
transition-duration: 50ms;
transition-property: opacity;
}
}
}
.media-minimal-skin .media-seek-indicator {
gap: calc(var(--media-spacing) * 1);
.media-seek-indicator__value {
font-variant-numeric: tabular-nums;
}
@container media-root (width > 24rem) {
padding: calc(var(--media-spacing) * 6);
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
.media-icon--seek {
display: block;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[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 shared
by the HTML and React skins.
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon,
.media-button--mute .media-icon,
.media-button--fullscreen .media-icon,
.media-button--pip .media-icon,
.media-button--cast .media-icon,
.media-button--airplay .media-icon,
.media-button--captions .media-icon {
opacity: 0;
}
.media-button--play .media-icon {
scale: 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 {
opacity: 1;
scale: 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;
}
@media (prefers-reduced-motion: reduce) {
.media-button--airplay {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
}
/* Container */
.media-minimal-skin--video {
--media-default-accent-color: oklch(1 0 0);
--media-border-color: light-dark(oklch(0 0 0 / 0.15), oklch(1 0 0 / 0.15));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-video-border-radius: var(--media-container-border-radius);
--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-popover-backdrop-filter: blur(16px) saturate(1.5);
--media-popover-background-color: oklch(0 0 0 / 0.5);
--media-popover-border-color: oklch(1 0 0 / 0.1);
--media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
--media-tooltip-background-color: var(--media-popover-background-color);
--media-tooltip-border-color: var(--media-popover-border-color);
--media-tooltip-text-color: currentColor;
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-error__dialog {
scale: 1;
transition-property: opacity;
}
}
@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);
}
&: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-container-border-radius: 0;
&::after {
display: none;
}
@media (width >= 1280px) {
--media-scale: 1.25;
}
@media (width >= 1536px) {
--media-scale: 1.5;
}
@media (width >= 1920px) {
--media-scale: 1.75;
}
}
* {
--media-focus-ring-color: oklch(1 0 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: calc(var(--media-spacing) * 3);
width: 100%;
max-width: calc(var(--media-spacing) * 64);
padding: calc(var(--media-spacing) * 4);
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__content {
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 2);
padding: calc(var(--media-spacing) * 1.5) 0;
}
.media-minimal-skin--video .media-error__title {
font-size: var(--media-font-size-medium);
}
.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.95;
}
.media-minimal-skin--video .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
/* Controls (hide/show behavior) */
.media-minimal-skin--video .media-controls {
--media-base-side-offset: 5;
--media-base-boundary-offset: 1;
--media-inset-factor: 1;
--media-inset: calc(var(--media-spacing) * var(--media-inset-factor));
--media-volume-mask: linear-gradient(to right, transparent 10%, #000 25%, #000 100%);
position: absolute;
inset-inline: var(--media-inset);
bottom: var(--media-inset);
z-index: 10;
flex-wrap: wrap;
column-gap: calc(var(--media-spacing) * 2);
color: oklch(1 0 0);
border-radius: calc(var(--media-spacing) * 3);
transition-timing-function: var(--media-controls-transition-timing-function);
/* Speed up the entry transition */
transition-duration: calc(var(--media-controls-transition-duration) / 2);
@media (pointer: fine) {
transition-property: translate, filter, opacity;
}
@media (pointer: coarse) {
transition-property: translate, opacity;
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 100%;
}
}
.media-time-controls {
--media-slider-height: calc(var(--media-spacing) * 5);
flex: 0 0 100%;
order: -1;
padding-inline: calc(var(--media-spacing) * 1.5);
}
.media-button-group:first-child {
flex: 1;
text-align: left;
}
.media-button-group:last-child {
flex: 1;
justify-content: end;
}
:is(.media-time-controls, .media-button-group:last-child) {
mask-image: var(--media-volume-mask-image, none);
mask-repeat: no-repeat;
mask-position: var(--media-volume-mask-position, 100% 0);
mask-size: var(--media-volume-mask-size, 200% 100%);
transition: mask-position 50ms ease-out;
}
&:has(.media-button--mute[aria-expanded="true"]) {
@container media-root (width <= 42rem) {
.media-button-group:last-child {
--media-volume-mask-image: var(--media-volume-mask);
--media-volume-mask-position: 0 0;
--media-volume-mask-size: 400% 100%;
}
}
@container media-root (width > 42rem) {
.media-time-controls {
--media-volume-mask-image: var(--media-volume-mask);
--media-volume-mask-position: 0 0;
}
}
}
@container media-root (width > 42rem) {
--media-inset-factor: 2;
--media-base-side-offset: 2;
flex-wrap: nowrap;
.media-time-controls {
--media-slider-height: calc(var(--media-spacing) * 8);
flex: 1;
order: unset;
}
.media-button-group:first-child,
.media-button-group:last-child {
flex: 0 0 auto;
}
}
}
.media-minimal-skin--video:has(.media-error[data-open]) .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-minimal-skin--video:has(.media-controls:not([data-visible])) {
cursor: none;
}
/* Popups & Animations */
.media-minimal-skin--video .media-popover--volume {
--media-popover-side-offset: 0rem;
padding: 0 calc(var(--media-spacing) * 3);
background: transparent;
}
/* Slider preview */
.media-minimal-skin--video .media-slider__value {
padding-inline: calc(var(--media-spacing) * 3);
text-shadow: 0 1px 0 var(--media-shadow-current-color);
}
.media-minimal-skin--video .media-slider .media-slider__preview {
--media-preview-end-inset: calc(100cqi - 100%);
--media-preview-left: clamp(
calc(var(--media-max-size) / 2),
var(--media-slider-pointer),
calc(100% - var(--media-max-size) / 2 + var(--media-preview-end-inset))
);
@container media-root (width > 42rem) {
--media-preview-left: var(--media-slider-pointer);
}
}
Minimal audio skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { playbackRateText } from '@videojs/core/i18n/text/menu';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { useTranslator, Container, usePlayer, BufferingIndicator, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, PlaybackRateRadioGroup as PlaybackRateRadioGroupComponent, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster } from '@videojs/react';
import { Audio } from '@videojs/react/audio';
import './player.css';
import { Player } from './player';
function PlaybackRateRadioGroup(): ReactNode {
const t = useTranslator();
return (
<PlaybackRateRadioGroupComponent
className="media-menu__group"
aria-label={t(playbackRateText)}
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="media-menu__item">
<span>{item.label}</span>
<Menu.ItemIndicator checked={item.checked} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
);
}
function PlaybackRateTrigger(): ReactNode {
const state = usePlaybackRateOptions();
if (!state) return null;
return (
<Tooltip.Root side="top" boundary="viewport">
<Tooltip.Trigger
render={
<Menu.Trigger
disabled={state.disabled}
render={<PlaybackRateButton className="media-button--playback-rate" render={<Button />} />}
/>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
// ================================================================
// Player
// ================================================================
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>
<Container className={`media-minimal-skin media-minimal-skin--audio ${className ?? ''}`} {...rest}>
<Audio src={src} />
<ErrorDialog.Root>
<ErrorDialog.Backdrop className="media-overlay media-error-backdrop" />
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title"></ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--subtle"></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.Buffer className="media-slider__buffer" />
<TimeSlider.Fill className="media-slider__fill" />
</TimeSlider.Track>
<TimeSlider.Thumb className="media-slider__thumb" />
<TimeSlider.Preview className="media-slider__preview">
<TimeSlider.Value type="pointer" className="media-tooltip media-slider__value media-time" />
</TimeSlider.Preview>
</TimeSlider.Root>
</div>
<div className="media-button-group">
<VolumePopover />
<Menu.Root side="top" align="center" boundary="viewport">
<PlaybackRateTrigger />
<Menu.Popup className="media-popover media-menu media-menu--playback-rate">
<Menu.Content className="media-menu__content">
<PlaybackRateRadioGroup />
</Menu.Content>
</Menu.Popup>
</Menu.Root>
</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 className="media-sr-only" />
</Container>
</Player>
);
}
// ================================================================
// 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 volumeUnavailable = usePlayer((s) => s.volumeAvailability !== 'available');
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 (volumeUnavailable) {
return (
<Tooltip.Root side="top" delay={0} sticky>
<Tooltip.Trigger render={muteButton} />
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="left" boundary="viewport">
<Tooltip.Root side="top" delay={0} sticky>
<Tooltip.Trigger render={<Popover.Trigger render={muteButton} />} />
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<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>
);
}
import { createPlayer } from '@videojs/react';
import { audioFeatures } from '@videojs/react/audio';
export const { Player } = createPlayer({ features: audioFeatures });
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-minimal-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 0.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-minimal-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-minimal-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * (var(--media-base-boundary-offset, 0) + 1));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 3);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row;
}
}
.media-minimal-skin .media-time-group {
display: flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
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;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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;
}
}
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9.5);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(var(--media-spacing) * 2);
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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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-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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-minimal-skin .media-button {
scale: 1;
transition-property: background-color;
will-change: auto;
}
}
.media-minimal-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-badge {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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(var(--media-spacing) * 1);
}
.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,
.media-minimal-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-minimal-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.media-minimal-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: var(--media-track-border-radius);
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.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: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: var(--media-track-border-radius);
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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%;
&[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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:focus-visible,
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 28;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: 100%;
height: calc(var(--media-spacing) * 1);
@container media-root (width > 32rem) {
--media-max-size-factor: 36;
}
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: var(--media-preview-left, var(--media-slider-pointer));
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: filter, opacity, scale;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 11));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 5));
display: flex;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 2);
justify-content: center;
}
.media-slider__chapter-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
z-index: 1;
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
&[data-orientation="horizontal"]::before {
top: 50%;
left: var(--media-slider-pointer);
width: 1px;
height: calc(var(--media-spacing) * 5);
}
&[data-orientation="vertical"]::before {
top: calc(100% - var(--media-slider-pointer));
left: 50%;
width: calc(var(--media-spacing) * 5);
height: 1px;
}
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail) {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(var(--media-spacing) * 2);
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--media-popup-translate-x-distance, 0), var(--media-popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
filter: blur(4px);
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2);
font-size: var(--media-font-size-base);
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 1);
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: calc(var(--media-spacing) * -1);
font-family: inherit;
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-minimal-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 2.5);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--media-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) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin: calc(var(--media-spacing) * 1) 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size 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 shared
by the HTML and React skins.
========================================================================== */
/* --- 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;
}
/* Container */
.media-minimal-skin--audio {
--media-default-accent-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-controls-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-controls-backdrop-filter: blur(16px) saturate(1.5);
--media-controls-border-color: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.1));
--media-controls-text-color: light-dark(oklch(0 0 0), oklch(1 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-popover-backdrop-filter: blur(16px) saturate(1.5);
--media-popover-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-popover-border-color: oklch(0 0 0 / 0.1);
--media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
--media-tooltip-background-color: var(--media-popover-background-color);
--media-tooltip-border-color: var(--media-popover-border-color);
--media-tooltip-text-color: currentColor;
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-tooltip-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
}
}
/* Error Dialog */
.media-minimal-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: calc(var(--media-spacing) * 4);
align-items: center;
padding-inline: calc(var(--media-spacing) * 5) calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 2);
align-items: center;
}
/* Controls */
.media-minimal-skin--audio .media-controls {
--media-base-side-offset: 2;
--media-base-boundary-offset: 2;
gap: calc(var(--media-spacing) * 2);
color: var(--media-controls-text-color);
border-radius: var(--media-border-radius, calc(var(--media-spacing) * 3.5));
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 {
--media-popover-side-offset: 0rem;
padding: 0 calc(var(--media-spacing) * 2) 0 calc(var(--media-spacing) * 16);
background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%);
}
.media-minimal-skin--audio .media-slider .media-slider__preview .media-slider__value {
bottom: calc(var(--media-spacing) * 10);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/audio-minimal.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>
<media-dialog-backdrop class="media-overlay media-error-backdrop"></media-dialog-backdrop>
<media-dialog-popup class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-dialog-title class="media-error__title"></media-dialog-title>
<media-dialog-description class="media-error__description"></media-dialog-description>
</div>
<div class="media-error__actions">
<media-dialog-close class="media-button media-button--subtle"></media-dialog-close>
</div>
</div>
</media-dialog-popup>
</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-buffer class="media-slider__buffer"></media-slider-buffer>
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</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-tooltip media-slider__value media-time"></media-slider-value>
</media-slider-preview>
</media-time-slider>
</div>
<div class="media-button-group">
<media-mute-button id="audio-mute-trigger" 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-tooltip trigger="audio-mute-trigger" delay="0" sticky side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<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>
<media-playback-rate-button id="playback-rate-trigger" 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-menu-content class="media-menu__content">
<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-content>
</media-menu>
<media-tooltip trigger="playback-rate-trigger" 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>
</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,
media-i18n,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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.
*/
media-container video,
media-container [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
media-container 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,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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 the volume popover when its slider is unavailable. */
.media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-minimal-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 0.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-minimal-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-minimal-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * (var(--media-base-boundary-offset, 0) + 1));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 3);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row;
}
}
.media-minimal-skin .media-time-group {
display: flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
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;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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;
}
}
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9.5);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(var(--media-spacing) * 2);
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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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-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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-minimal-skin .media-button {
scale: 1;
transition-property: background-color;
will-change: auto;
}
}
.media-minimal-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-badge {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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(var(--media-spacing) * 1);
}
.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,
.media-minimal-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-minimal-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.media-minimal-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: var(--media-track-border-radius);
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.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: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: var(--media-track-border-radius);
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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%;
&[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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:focus-visible,
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 28;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: 100%;
height: calc(var(--media-spacing) * 1);
@container media-root (width > 32rem) {
--media-max-size-factor: 36;
}
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: var(--media-preview-left, var(--media-slider-pointer));
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: filter, opacity, scale;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 11));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 5));
display: flex;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 2);
justify-content: center;
}
.media-slider__chapter-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
z-index: 1;
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
&[data-orientation="horizontal"]::before {
top: 50%;
left: var(--media-slider-pointer);
width: 1px;
height: calc(var(--media-spacing) * 5);
}
&[data-orientation="vertical"]::before {
top: calc(100% - var(--media-slider-pointer));
left: 50%;
width: calc(var(--media-spacing) * 5);
height: 1px;
}
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail) {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(var(--media-spacing) * 2);
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--media-popup-translate-x-distance, 0), var(--media-popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
filter: blur(4px);
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2);
font-size: var(--media-font-size-base);
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 1);
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: calc(var(--media-spacing) * -1);
font-family: inherit;
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-minimal-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 2.5);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--media-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) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin: calc(var(--media-spacing) * 1) 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size 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 shared
by the HTML and React skins.
========================================================================== */
/* --- 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;
}
/* Container */
.media-minimal-skin--audio {
--media-default-accent-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-controls-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-controls-backdrop-filter: blur(16px) saturate(1.5);
--media-controls-border-color: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.1));
--media-controls-text-color: light-dark(oklch(0 0 0), oklch(1 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-popover-backdrop-filter: blur(16px) saturate(1.5);
--media-popover-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-popover-border-color: oklch(0 0 0 / 0.1);
--media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
--media-tooltip-background-color: var(--media-popover-background-color);
--media-tooltip-border-color: var(--media-popover-border-color);
--media-tooltip-text-color: currentColor;
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-tooltip-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
}
}
/* Error Dialog */
.media-minimal-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: calc(var(--media-spacing) * 4);
align-items: center;
padding-inline: calc(var(--media-spacing) * 5) calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 2);
align-items: center;
}
/* Controls */
.media-minimal-skin--audio .media-controls {
--media-base-side-offset: 2;
--media-base-boundary-offset: 2;
gap: calc(var(--media-spacing) * 2);
color: var(--media-controls-text-color);
border-radius: var(--media-border-radius, calc(var(--media-spacing) * 3.5));
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 {
--media-popover-side-offset: 0rem;
padding: 0 calc(var(--media-spacing) * 2) 0 calc(var(--media-spacing) * 16);
background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%);
}
.media-minimal-skin--audio .media-slider .media-slider__preview .media-slider__value {
bottom: calc(var(--media-spacing) * 10);
}
Default live video skin
The live skins swap the duration and current-time displays for a Live button. They build on the live presets, so the player reports live-edge state.
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { captionsText } from '@videojs/core/i18n/text/menu';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, FullscreenEnterIcon, FullscreenExitIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, RestartIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { Poster, useTranslator, Container, usePlayer, AirPlayButton, BufferingIndicator, CaptionsButton, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, LiveButton, Menu, MuteButton, PiPButton, PlayButton, Popover, StatusAnnouncer, StatusIndicator, Tooltip, VolumeIndicator, VolumeSlider } from '@videojs/react';
import { HlsJsVideo } from '@videojs/react/media/hlsjs-video';
import './player.css';
import { Player } from './player';
const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;
const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;
function CaptionsTrigger(): ReactNode {
const t = useTranslator();
const captions = useCaptionsOptions();
if (!captions) return null;
const { disabled } = captions;
if (!captions.showMenu) {
return (
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CaptionsButton className="media-button--captions" render={<Button />}>
<CaptionsOffIcon className="media-icon media-icon--captions-off" />
<CaptionsOnIcon className="media-icon media-icon--captions-on" />
</CaptionsButton>
}
/>
<Tooltip.Popup className="media-surface media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
const { options, setValue, value } = captions;
return (
<Menu.Root side="top" align="center">
<Menu.Trigger
disabled={disabled}
render={
<CaptionsButton className="media-button--captions" render={<Button />}>
<CaptionsOffIcon className="media-icon media-icon--captions-off" />
<CaptionsOnIcon className="media-icon media-icon--captions-on" />
</CaptionsButton>
}
/>
<Menu.Popup className="media-surface media-popover media-menu media-menu--captions">
<Menu.Content className="media-menu__content">
<Menu.RadioGroup
className="media-menu__group"
value={value}
onValueChange={setValue}
aria-label={t(captionsText)}
>
{options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<bdi dir="auto">{option.label}</bdi>
<Menu.ItemIndicator checked={option.value === value} forceMount className="media-menu__indicator">
<CheckIcon className="media-icon" />
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Popup>
</Menu.Root>
);
}
// ================================================================
// Player
// ================================================================
export interface LiveVideoPlayerProps {
src: string;
poster?: string | undefined;
style?: CSSProperties;
className?: string;
renderPoster?: Poster.Props['render'];
}
/**
* @example
* ```tsx
* <LiveVideoPlayer
* src="https://stream.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM.m3u8"
* poster="https://image.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM/thumbnail.webp"
* />
* ```
*/
export function LiveVideoPlayer({ src, poster, className, renderPoster, style, ...rest }: LiveVideoPlayerProps): ReactNode {
return (
<Player poster={poster}>
<Container className={`media-default-skin media-default-skin--video ${className ?? ''}`} style={style} {...rest}>
<HlsJsVideo src={src} playsInline />
<Poster render={renderPoster} />
<BufferingIndicator
render={(props) => (
<div {...props} className="media-buffering-indicator">
<SpinnerIcon className="media-icon" />
</div>
)}
/>
<ErrorDialog.Root>
<ErrorDialog.Backdrop className="media-overlay media-error-backdrop" />
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog media-surface">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title"></ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--primary"></ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root>
<Controls.Backdrop className="media-overlay" />
<Controls.Content className="media-surface media-controls media-controls--root">
<Tooltip.Provider>
<div className="media-surface media-controls media-controls--primary">
<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>
<LiveButton className="media-button media-button--subtle media-button--live" />
</div>
<div className="media-time-controls" aria-hidden="true" />
<div className="media-button-group">
<VolumePopover />
<CaptionsTrigger />
<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>
</div>
</Tooltip.Provider>
</Controls.Content>
</Controls.Root>
{/* 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="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
{/* Gestures */}
<Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
<Gesture type="tap" action="toggleControls" pointer="touch" />
<Gesture type="doubletap" action="toggleFullscreen" region="center" />
{/* Input Indicators */}
<StatusAnnouncer className="media-sr-only" />
<div className="media-input-indicator-overlay">
<VolumeIndicator.Root className="media-surface media-volume-indicator">
<VolumeIndicator.Fill className="media-volume-indicator__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-volume-indicator__value" />
</VolumeIndicator.Fill>
</VolumeIndicator.Root>
<StatusIndicator.Root
actions={TOP_STATUS_ACTIONS}
className="media-surface media-status-indicator media-status-indicator--state"
>
<div className="media-status-indicator__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-status-indicator__value" />
</div>
</StatusIndicator.Root>
<StatusIndicator.Root
actions={CENTER_STATUS_ACTIONS}
className="media-status-indicator media-status-indicator--playback"
>
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</StatusIndicator.Root>
</div>
</Container>
</Player>
);
}
// ================================================================
// 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 volumeUnavailable = usePlayer((s) => s.volumeAvailability !== 'available');
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 (volumeUnavailable) 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>
);
}
import { createPlayer } from '@videojs/react';
import { liveVideoFeatures } from '@videojs/react/live-video';
export const { Player } = createPlayer({ features: liveVideoFeatures });
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-default-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 1.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius, 1.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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 1px 0 0 var(--media-surface-inner-border-color),
inset 0 0 0 1px oklch(from var(--media-surface-inner-border-color) l c h / calc(alpha * 0.5));
}
}
.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-container-border-radius);
}
.media-default-skin video {
border-radius: inherit;
}
.media-default-skin:fullscreen ::slotted(video),
.media-default-skin:fullscreen video {
object-fit: contain;
}
.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-backdrop {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-default-skin .media-controls-backdrop[data-visible],
.media-default-skin .media-error-backdrop[data-open] {
opacity: 1;
}
.media-default-skin:has(.media-buffering-indicator[data-visible]) .media-controls-backdrop {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-default-skin .media-error-backdrop[data-open] {
backdrop-filter: blur(16px) saturate(1.5);
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-default-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-default-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * var(--media-base-boundary-offset, 2));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
border-radius: calc(Infinity * 1px);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: calc(var(--media-spacing) * 2.5);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row-reverse;
}
> .media-time:last-child {
@container media-time-controls (width < 16rem) {
display: none;
}
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
}
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
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, color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-default-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-default-skin .media-button {
scale: 1;
transition-property: background-color, color;
will-change: auto;
}
}
.media-default-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-badge {
padding: calc(var(--media-spacing) * 0.5) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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);
}
.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,
.media-default-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-default-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.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]) {
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-container-border-radius);
}
.media-default-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
.media-default-skin:fullscreen media-poster ::slotted(img),
.media-default-skin:fullscreen media-poster img,
.media-default-skin:fullscreen > img {
object-fit: contain;
}
.media-default-skin .media-thumbnail {
position: relative;
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: calc(var(--media-spacing) * 3);
.media-thumbnail__image {
position: relative;
display: block;
max-width: var(--media-thumbnail-max-width);
max-height: var(--media-thumbnail-max-height);
overflow: clip;
border-radius: inherit;
&::after {
position: absolute;
inset: 0;
content: "";
background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.1), oklch(0 0 0 / 0));
border-radius: inherit;
}
}
.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;
}
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
&:has(.media-thumbnail__image[data-loading]) {
width: var(--media-thumbnail-max-width);
max-width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
.media-thumbnail__image {
opacity: 0;
}
.media-thumbnail__spinner {
opacity: 1;
}
}
}
.media-default-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: var(--media-track-border-radius);
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: inherit;
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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;
scale: 0.8;
translate: -50% -50%;
&[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: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
opacity: 1;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (hover: hover) and (pointer: fine) {
&:hover {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
&::after {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 36;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: var(--media-max-size);
height: calc(var(--media-spacing) * 1);
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: 50%;
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 9));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 10.5));
display: flex;
flex-direction: column;
align-items: center;
}
.media-slider__chapter-title {
min-width: 0;
max-width: var(--media-max-size);
padding-inline: calc(var(--media-spacing) * 6);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
width: calc(var(--media-spacing) * 1);
height: calc(var(--media-spacing) * 1);
pointer-events: none;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-shadow-current-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail),
.media-slider__preview[data-pointing]:not([data-dragging])::before {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(0.5 * var(--media-internal-scale-unit));
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(--media-popup-translate-x-distance, 0), var(--media-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"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 3) 0;
border-radius: calc(Infinity * 1px);
&:has(media-volume-slider[data-hidden]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
font-size: var(--media-font-size-base);
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: calc(var(--media-spacing) * 1);
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: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-default-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 3);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--media-menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin-block: calc(var(--media-spacing) * 1);
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
opacity: 0;
.media-icon {
filter: drop-shadow(0 1px 0 var(--media-shadow-current-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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
.media-default-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: calc(var(--media-spacing) * -2);
&:has(.media-controls[data-visible]) {
--media-caption-track-y: calc(var(--media-spacing) * -14);
}
}
.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);
}
.media-default-skin .media-input-indicator-overlay {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
color: oklch(1 0 0);
pointer-events: none;
}
.media-default-skin {
.media-volume-indicator,
.media-status-indicator--state {
--media-surface-background-color: oklch(0 0 0 / 0.25);
position: absolute;
top: calc(var(--media-spacing) * 3);
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-volume-indicator__content,
.media-status-indicator__content {
display: flex;
gap: calc(var(--media-spacing) * 2);
align-items: center;
justify-content: space-between;
width: 100%;
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
* {
mix-blend-mode: difference;
}
}
.media-icon {
display: none;
flex-shrink: 0;
}
.media-volume-indicator__value,
.media-status-indicator__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);
}
&[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;
}
}
&[data-ending-style] {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -25%;
}
}
}
.media-seek-indicator,
.media-status-indicator--playback {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: calc(var(--media-spacing) * 4);
text-align: center;
}
}
.media-default-skin .media-volume-indicator {
width: min(80%, calc(var(--media-spacing) * 48));
transform: translateX(0);
.media-volume-indicator__content {
background-image: linear-gradient(currentColor, currentColor);
background-repeat: no-repeat;
background-position: left;
background-size: var(--media-volume-fill, 0%) 100%;
border-radius: inherit;
transition: background-size 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]:not([data-starting-style], [data-ending-style]),
&[data-max]:not([data-starting-style], [data-ending-style]) {
transform: translateX(0.25px);
transition: transform 300ms linear(0, -24 20%, 16 40%, -8 60%, 4 80%, 1);
}
}
}
.media-default-skin .media-status-indicator--state {
&[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;
}
}
.media-default-skin .media-status-indicator--playback {
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;
.media-icon {
grid-area: 1 / 1;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
opacity: 0;
scale: 0;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
&.media-icon--play {
translate: 1px 0;
}
}
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
opacity: 1;
scale: 1;
}
&[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;
&[data-starting-style],
&[data-ending-style],
.media-icon {
scale: 1;
}
.media-icon {
transition-duration: 50ms;
transition-property: opacity;
}
}
}
.media-default-skin .media-seek-indicator {
gap: calc(var(--media-spacing) * 1);
.media-seek-indicator__value {
font-variant-numeric: tabular-nums;
}
@container media-root (width > 24rem) {
padding: calc(var(--media-spacing) * 6);
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
.media-icon--seek {
display: block;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[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 shared
by the HTML and React skins.
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon,
.media-button--mute .media-icon,
.media-button--fullscreen .media-icon,
.media-button--pip .media-icon,
.media-button--cast .media-icon,
.media-button--airplay .media-icon,
.media-button--captions .media-icon {
opacity: 0;
}
.media-button--play .media-icon {
scale: 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 {
opacity: 1;
scale: 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;
}
@media (prefers-reduced-motion: reduce) {
.media-button--airplay {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
}
/* Video-specific container styles */
.media-default-skin--video {
--media-default-accent-color: oklch(1 0 0);
--media-border-color: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.15));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-video-border-radius: var(--media-container-border-radius);
--media-surface-background-color: oklch(1 0 0 / 0.1);
--media-surface-inner-border-color: oklch(1 0 0 / 0.1);
--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-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: ease-out;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
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-error__dialog {
scale: 1;
transition-property: opacity;
}
}
@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--root: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-container-border-radius: 0;
&::after {
display: none;
}
@media (width >= 1280px) {
--media-scale: 1.25;
}
@media (width >= 1536px) {
--media-scale: 1.5;
}
@media (width >= 1920px) {
--media-scale: 1.75;
}
}
* {
--media-focus-ring-color: oklch(1 0 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: calc(var(--media-spacing) * 3);
width: 100%;
max-width: calc(var(--media-spacing) * 72);
padding: calc(var(--media-spacing) * 3);
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.25);
border-radius: calc(var(--media-spacing) * 7);
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.95;
}
.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: calc(var(--media-spacing) * 2);
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 1.5);
text-shadow: inherit;
}
.media-default-skin--video .media-error__title {
font-size: var(--media-font-size-medium);
}
.media-default-skin--video .media-slider__value {
text-shadow: 0 1px 0 var(--media-shadow-current-color);
}
/* Controls (hide/show behavior) */
.media-default-skin--video .media-controls--root {
--media-inset-factor: 2;
--media-inset: calc(var(--media-spacing) * var(--media-inset-factor));
--media-base-boundary-offset: var(--media-inset-factor);
z-index: 10;
display: contents;
color: oklch(1 0 0);
transition-timing-function: var(--media-controls-transition-timing-function);
/* Speed up the entry transition */
transition-duration: calc(var(--media-controls-transition-duration) / 2);
.media-controls--primary,
.media-controls--secondary {
position: absolute;
}
/* Primary must be higher for the preview/thumbnail */
.media-controls--primary {
z-index: 20;
}
.media-controls--secondary {
z-index: 10;
}
.media-controls--primary {
inset-inline: var(--media-inset);
bottom: var(--media-inset);
transform-origin: bottom;
}
.media-controls--secondary {
top: var(--media-inset);
right: var(--media-inset);
container-type: normal;
transform-origin: top;
}
.media-time-controls {
flex: 1;
padding-inline: calc(var(--media-spacing) * 2);
}
@container media-root (width < 32rem) {
.media-controls--primary,
.media-controls--secondary {
transition-timing-function: inherit;
transition-duration: inherit;
@media (pointer: fine) {
transition-property: filter, opacity, scale, translate;
}
@media (pointer: coarse) {
transition-property: opacity, scale, translate;
}
}
&:after {
display: none;
}
&:not([data-visible]) {
.media-controls--primary,
.media-controls--secondary {
pointer-events: none;
opacity: 0;
scale: 0.95;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
}
.media-controls--primary {
@media (prefers-reduced-motion: no-preference) {
translate: 0 4px;
}
}
.media-controls--secondary {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -4px;
}
}
}
.media-button--captions {
display: none;
}
}
@container media-root (width >= 32rem) {
position: absolute;
inset-inline: var(--media-inset);
bottom: var(--media-inset);
display: flex;
transform-origin: bottom;
.media-controls--primary,
.media-controls--secondary {
display: contents;
&::after {
display: none;
}
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
scale: 0.95;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 4px;
}
}
.media-time-controls {
padding-inline: calc(var(--media-spacing) * 3);
}
}
@media (pointer: fine) {
transition-property: filter, opacity, scale, translate;
}
@media (pointer: coarse) {
transition-property: opacity, scale, translate;
}
@container media-root (width > 42rem) {
--media-inset-factor: 3;
}
}
.media-default-skin--video:has(.media-error[data-open]) .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-default-skin--video:has(.media-controls--root:not([data-visible])) {
cursor: none;
}
/* Sliders */
.media-default-skin--video .media-slider__track {
background-color: oklch(1 0 0 / 0.2);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/live-video.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/media/hlsjs-video.js"></script>
<link rel="stylesheet" href="./player.css">
<live-video-player poster="https://image.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM/thumbnail.webp">
<media-container class="media-default-skin media-default-skin--video">
<hlsjs-video src="https://stream.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM.m3u8" playsinline></hlsjs-video>
<media-poster>
<img alt="" decoding="async" />
</media-poster>
<media-buffering-indicator class="media-buffering-indicator">
<media-icon name="spinner" class="media-icon"></media-icon>
</media-buffering-indicator>
<media-error-dialog>
<media-dialog-backdrop class="media-overlay media-error-backdrop"></media-dialog-backdrop>
<media-dialog-popup class="media-error">
<div class="media-error__dialog media-surface">
<div class="media-error__content">
<media-dialog-title class="media-error__title"></media-dialog-title>
<media-dialog-description class="media-error__description"></media-dialog-description>
</div>
<div class="media-error__actions">
<media-dialog-close class="media-button media-button--primary"></media-dialog-close>
</div>
</div>
</media-dialog-popup>
</media-error-dialog>
<media-controls>
<media-controls-backdrop class="media-overlay"></media-controls-backdrop>
<media-controls-content class="media-surface media-controls media-controls--root">
<media-tooltip-group>
<div class="media-surface media-controls media-controls--primary">
<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-live-button class="media-button media-button--subtle media-button--live"></media-live-button>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
<div class="media-button-group">
<media-mute-button commandfor="live-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="live-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>
<media-captions-button menu-for="captions-menu" commandfor="captions-tooltip" class="media-button media-button--subtle media-button--icon media-button--captions">
<media-icon name="captions-off" class="media-icon media-icon--captions-off"></media-icon>
<media-icon name="captions-on" class="media-icon media-icon--captions-on"></media-icon>
</media-captions-button>
<media-menu id="captions-menu" side="top" align="center" class="media-surface media-popover media-menu media-menu--captions">
<media-menu-content class="media-menu__content">
<media-captions-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<bdi data-part="label" dir="auto"></bdi>
<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-content>
</media-menu>
<media-tooltip id="captions-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-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>
</div>
</media-tooltip-group>
</media-controls-content>
</media-controls>
<!-- 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="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></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="toggleFullscreen" region="center"></media-gesture>
<!-- Input Indicators -->
<media-status-announcer class="media-sr-only"></media-status-announcer>
<div class="media-input-indicator-overlay">
<media-volume-indicator hidden class="media-surface media-volume-indicator">
<media-volume-indicator-fill class="media-volume-indicator__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-volume-indicator__value"></media-volume-indicator-value>
</media-volume-indicator-fill>
</media-volume-indicator>
<media-status-indicator
hidden
actions="toggleSubtitles toggleFullscreen togglePictureInPicture"
class="media-surface media-status-indicator media-status-indicator--state"
>
<div class="media-status-indicator__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-status-indicator__value"></media-status-indicator-value>
</div>
</media-status-indicator>
<media-status-indicator hidden actions="togglePaused" class="media-status-indicator media-status-indicator--playback">
<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>
</live-video-player>/* -------------------------------------------------------------------------- */
/* Global styles for the host document, outside of the Shadow DOM */
/* -------------------------------------------------------------------------- */
video-player,
live-video-player,
media-i18n,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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.
*/
media-container video,
media-container [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
media-container 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,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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 the volume popover when its slider is unavailable. */
.media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-default-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 1.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius, 1.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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 1px 0 0 var(--media-surface-inner-border-color),
inset 0 0 0 1px oklch(from var(--media-surface-inner-border-color) l c h / calc(alpha * 0.5));
}
}
.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-container-border-radius);
}
.media-default-skin video {
border-radius: inherit;
}
.media-default-skin:fullscreen ::slotted(video),
.media-default-skin:fullscreen video {
object-fit: contain;
}
.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-backdrop {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-default-skin .media-controls-backdrop[data-visible],
.media-default-skin .media-error-backdrop[data-open] {
opacity: 1;
}
.media-default-skin:has(.media-buffering-indicator[data-visible]) .media-controls-backdrop {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-default-skin .media-error-backdrop[data-open] {
backdrop-filter: blur(16px) saturate(1.5);
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-default-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-default-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * var(--media-base-boundary-offset, 2));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
border-radius: calc(Infinity * 1px);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: calc(var(--media-spacing) * 2.5);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row-reverse;
}
> .media-time:last-child {
@container media-time-controls (width < 16rem) {
display: none;
}
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
}
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
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, color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-default-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-default-skin .media-button {
scale: 1;
transition-property: background-color, color;
will-change: auto;
}
}
.media-default-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-badge {
padding: calc(var(--media-spacing) * 0.5) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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);
}
.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,
.media-default-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-default-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.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]) {
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-container-border-radius);
}
.media-default-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
.media-default-skin:fullscreen media-poster ::slotted(img),
.media-default-skin:fullscreen media-poster img,
.media-default-skin:fullscreen > img {
object-fit: contain;
}
.media-default-skin .media-thumbnail {
position: relative;
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: calc(var(--media-spacing) * 3);
.media-thumbnail__image {
position: relative;
display: block;
max-width: var(--media-thumbnail-max-width);
max-height: var(--media-thumbnail-max-height);
overflow: clip;
border-radius: inherit;
&::after {
position: absolute;
inset: 0;
content: "";
background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.1), oklch(0 0 0 / 0));
border-radius: inherit;
}
}
.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;
}
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
&:has(.media-thumbnail__image[data-loading]) {
width: var(--media-thumbnail-max-width);
max-width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
.media-thumbnail__image {
opacity: 0;
}
.media-thumbnail__spinner {
opacity: 1;
}
}
}
.media-default-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: var(--media-track-border-radius);
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: inherit;
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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;
scale: 0.8;
translate: -50% -50%;
&[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: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
opacity: 1;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (hover: hover) and (pointer: fine) {
&:hover {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
&::after {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 36;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: var(--media-max-size);
height: calc(var(--media-spacing) * 1);
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: 50%;
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 9));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 10.5));
display: flex;
flex-direction: column;
align-items: center;
}
.media-slider__chapter-title {
min-width: 0;
max-width: var(--media-max-size);
padding-inline: calc(var(--media-spacing) * 6);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
width: calc(var(--media-spacing) * 1);
height: calc(var(--media-spacing) * 1);
pointer-events: none;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-shadow-current-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail),
.media-slider__preview[data-pointing]:not([data-dragging])::before {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(0.5 * var(--media-internal-scale-unit));
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(--media-popup-translate-x-distance, 0), var(--media-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"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 3) 0;
border-radius: calc(Infinity * 1px);
&:has(media-volume-slider[data-hidden]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
font-size: var(--media-font-size-base);
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: calc(var(--media-spacing) * 1);
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: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-default-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 3);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--media-menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin-block: calc(var(--media-spacing) * 1);
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
opacity: 0;
.media-icon {
filter: drop-shadow(0 1px 0 var(--media-shadow-current-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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
.media-default-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: calc(var(--media-spacing) * -2);
&:has(.media-controls[data-visible]) {
--media-caption-track-y: calc(var(--media-spacing) * -14);
}
}
.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);
}
.media-default-skin .media-input-indicator-overlay {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
color: oklch(1 0 0);
pointer-events: none;
}
.media-default-skin {
.media-volume-indicator,
.media-status-indicator--state {
--media-surface-background-color: oklch(0 0 0 / 0.25);
position: absolute;
top: calc(var(--media-spacing) * 3);
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-volume-indicator__content,
.media-status-indicator__content {
display: flex;
gap: calc(var(--media-spacing) * 2);
align-items: center;
justify-content: space-between;
width: 100%;
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
* {
mix-blend-mode: difference;
}
}
.media-icon {
display: none;
flex-shrink: 0;
}
.media-volume-indicator__value,
.media-status-indicator__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);
}
&[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;
}
}
&[data-ending-style] {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -25%;
}
}
}
.media-seek-indicator,
.media-status-indicator--playback {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: calc(var(--media-spacing) * 4);
text-align: center;
}
}
.media-default-skin .media-volume-indicator {
width: min(80%, calc(var(--media-spacing) * 48));
transform: translateX(0);
.media-volume-indicator__content {
background-image: linear-gradient(currentColor, currentColor);
background-repeat: no-repeat;
background-position: left;
background-size: var(--media-volume-fill, 0%) 100%;
border-radius: inherit;
transition: background-size 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]:not([data-starting-style], [data-ending-style]),
&[data-max]:not([data-starting-style], [data-ending-style]) {
transform: translateX(0.25px);
transition: transform 300ms linear(0, -24 20%, 16 40%, -8 60%, 4 80%, 1);
}
}
}
.media-default-skin .media-status-indicator--state {
&[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;
}
}
.media-default-skin .media-status-indicator--playback {
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;
.media-icon {
grid-area: 1 / 1;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
opacity: 0;
scale: 0;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
&.media-icon--play {
translate: 1px 0;
}
}
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
opacity: 1;
scale: 1;
}
&[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;
&[data-starting-style],
&[data-ending-style],
.media-icon {
scale: 1;
}
.media-icon {
transition-duration: 50ms;
transition-property: opacity;
}
}
}
.media-default-skin .media-seek-indicator {
gap: calc(var(--media-spacing) * 1);
.media-seek-indicator__value {
font-variant-numeric: tabular-nums;
}
@container media-root (width > 24rem) {
padding: calc(var(--media-spacing) * 6);
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
.media-icon--seek {
display: block;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[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 shared
by the HTML and React skins.
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon,
.media-button--mute .media-icon,
.media-button--fullscreen .media-icon,
.media-button--pip .media-icon,
.media-button--cast .media-icon,
.media-button--airplay .media-icon,
.media-button--captions .media-icon {
opacity: 0;
}
.media-button--play .media-icon {
scale: 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 {
opacity: 1;
scale: 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;
}
@media (prefers-reduced-motion: reduce) {
.media-button--airplay {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
}
/* Video-specific container styles */
.media-default-skin--video {
--media-default-accent-color: oklch(1 0 0);
--media-border-color: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.15));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-video-border-radius: var(--media-container-border-radius);
--media-surface-background-color: oklch(1 0 0 / 0.1);
--media-surface-inner-border-color: oklch(1 0 0 / 0.1);
--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-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: ease-out;
--media-popup-transition-duration: 100ms;
--media-popup-transition-timing-function: ease-out;
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-error__dialog {
scale: 1;
transition-property: opacity;
}
}
@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--root: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-container-border-radius: 0;
&::after {
display: none;
}
@media (width >= 1280px) {
--media-scale: 1.25;
}
@media (width >= 1536px) {
--media-scale: 1.5;
}
@media (width >= 1920px) {
--media-scale: 1.75;
}
}
* {
--media-focus-ring-color: oklch(1 0 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: calc(var(--media-spacing) * 3);
width: 100%;
max-width: calc(var(--media-spacing) * 72);
padding: calc(var(--media-spacing) * 3);
color: oklch(1 0 0);
text-shadow: 0 1px 0 oklch(0 0 0 / 0.25);
border-radius: calc(var(--media-spacing) * 7);
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.95;
}
.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: calc(var(--media-spacing) * 2);
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 1.5);
text-shadow: inherit;
}
.media-default-skin--video .media-error__title {
font-size: var(--media-font-size-medium);
}
.media-default-skin--video .media-slider__value {
text-shadow: 0 1px 0 var(--media-shadow-current-color);
}
/* Controls (hide/show behavior) */
.media-default-skin--video .media-controls--root {
--media-inset-factor: 2;
--media-inset: calc(var(--media-spacing) * var(--media-inset-factor));
--media-base-boundary-offset: var(--media-inset-factor);
z-index: 10;
display: contents;
color: oklch(1 0 0);
transition-timing-function: var(--media-controls-transition-timing-function);
/* Speed up the entry transition */
transition-duration: calc(var(--media-controls-transition-duration) / 2);
.media-controls--primary,
.media-controls--secondary {
position: absolute;
}
/* Primary must be higher for the preview/thumbnail */
.media-controls--primary {
z-index: 20;
}
.media-controls--secondary {
z-index: 10;
}
.media-controls--primary {
inset-inline: var(--media-inset);
bottom: var(--media-inset);
transform-origin: bottom;
}
.media-controls--secondary {
top: var(--media-inset);
right: var(--media-inset);
container-type: normal;
transform-origin: top;
}
.media-time-controls {
flex: 1;
padding-inline: calc(var(--media-spacing) * 2);
}
@container media-root (width < 32rem) {
.media-controls--primary,
.media-controls--secondary {
transition-timing-function: inherit;
transition-duration: inherit;
@media (pointer: fine) {
transition-property: filter, opacity, scale, translate;
}
@media (pointer: coarse) {
transition-property: opacity, scale, translate;
}
}
&:after {
display: none;
}
&:not([data-visible]) {
.media-controls--primary,
.media-controls--secondary {
pointer-events: none;
opacity: 0;
scale: 0.95;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
}
.media-controls--primary {
@media (prefers-reduced-motion: no-preference) {
translate: 0 4px;
}
}
.media-controls--secondary {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -4px;
}
}
}
.media-button--captions {
display: none;
}
}
@container media-root (width >= 32rem) {
position: absolute;
inset-inline: var(--media-inset);
bottom: var(--media-inset);
display: flex;
transform-origin: bottom;
.media-controls--primary,
.media-controls--secondary {
display: contents;
&::after {
display: none;
}
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
scale: 0.95;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 4px;
}
}
.media-time-controls {
padding-inline: calc(var(--media-spacing) * 3);
}
}
@media (pointer: fine) {
transition-property: filter, opacity, scale, translate;
}
@media (pointer: coarse) {
transition-property: opacity, scale, translate;
}
@container media-root (width > 42rem) {
--media-inset-factor: 3;
}
}
.media-default-skin--video:has(.media-error[data-open]) .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-default-skin--video:has(.media-controls--root:not([data-visible])) {
cursor: none;
}
/* Sliders */
.media-default-skin--video .media-slider__track {
background-color: oklch(1 0 0 / 0.2);
}
Default live audio skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { PauseIcon, PlayIcon, RestartIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { Container, usePlayer, BufferingIndicator, ErrorDialog, Hotkey, LiveButton, MuteButton, PlayButton, Popover, StatusAnnouncer, Tooltip, VolumeSlider, type Poster } from '@videojs/react';
import { MuxAudio } from '@videojs/react/media/mux-audio';
import './player.css';
import { Player } from './player';
export function TooltipPopup(props: Omit<Tooltip.PopupProps, 'children' | 'className'>): ReactNode {
return (
<Tooltip.Popup className="media-surface media-tooltip" {...props}>
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
);
}
// ================================================================
// Player
// ================================================================
export interface LiveAudioPlayerProps {
src: string;
style?: CSSProperties;
className?: string;
}
/**
* @example
* ```tsx
* <LiveAudioPlayer
* src="https://stream.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM.m3u8"
* />
* ```
*/
export function LiveAudioPlayer({ src, className, ...rest }: LiveAudioPlayerProps): ReactNode {
return (
<Player>
<Container className={`media-default-skin media-default-skin--audio ${className ?? ''}`} {...rest}>
<MuxAudio src={src} />
<ErrorDialog.Root>
<ErrorDialog.Backdrop className="media-overlay media-error-backdrop" />
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title"></ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--subtle"></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>
}
/>
<TooltipPopup />
</Tooltip.Root>
</span>
<LiveButton className="media-button media-button--subtle media-button--live" />
</div>
<div className="media-time-controls" aria-hidden="true" />
<div className="media-button-group">
<VolumePopover />
</div>
</Tooltip.Provider>
</div>
{/* Hotkeys */}
<Hotkey keys="Space" action="togglePaused" />
<Hotkey keys="k" action="togglePaused" />
<Hotkey keys="m" action="toggleMuted" />
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
{/* Input Feedback */}
<StatusAnnouncer className="media-sr-only" />
</Container>
</Player>
);
}
// ================================================================
// 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 volumeUnavailable = usePlayer((s) => s.volumeAvailability !== 'available');
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 (volumeUnavailable) 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>
);
}
import { createPlayer } from '@videojs/react';
import { liveAudioFeatures } from '@videojs/react/live-audio';
export const { Player } = createPlayer({ features: liveAudioFeatures });
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-default-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 1.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius, 1.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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 1px 0 0 var(--media-surface-inner-border-color),
inset 0 0 0 1px oklch(from var(--media-surface-inner-border-color) l c h / calc(alpha * 0.5));
}
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-default-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-default-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * var(--media-base-boundary-offset, 2));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
border-radius: calc(Infinity * 1px);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: calc(var(--media-spacing) * 2.5);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row-reverse;
}
> .media-time:last-child {
@container media-time-controls (width < 16rem) {
display: none;
}
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
}
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
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, color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-default-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-default-skin .media-button {
scale: 1;
transition-property: background-color, color;
will-change: auto;
}
}
.media-default-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-badge {
padding: calc(var(--media-spacing) * 0.5) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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);
}
.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,
.media-default-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-default-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.media-default-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: var(--media-track-border-radius);
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: inherit;
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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;
scale: 0.8;
translate: -50% -50%;
&[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: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
opacity: 1;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (hover: hover) and (pointer: fine) {
&:hover {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
&::after {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 36;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: var(--media-max-size);
height: calc(var(--media-spacing) * 1);
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: 50%;
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 9));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 10.5));
display: flex;
flex-direction: column;
align-items: center;
}
.media-slider__chapter-title {
min-width: 0;
max-width: var(--media-max-size);
padding-inline: calc(var(--media-spacing) * 6);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
width: calc(var(--media-spacing) * 1);
height: calc(var(--media-spacing) * 1);
pointer-events: none;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-shadow-current-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail),
.media-slider__preview[data-pointing]:not([data-dragging])::before {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(0.5 * var(--media-internal-scale-unit));
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(--media-popup-translate-x-distance, 0), var(--media-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"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 3) 0;
border-radius: calc(Infinity * 1px);
&:has(media-volume-slider[data-hidden]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
font-size: var(--media-font-size-base);
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: calc(var(--media-spacing) * 1);
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: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-default-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 3);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--media-menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin-block: calc(var(--media-spacing) * 1);
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
opacity: 0;
.media-icon {
filter: drop-shadow(0 1px 0 var(--media-shadow-current-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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size 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 shared
by the HTML and React skins.
========================================================================== */
/* --- 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;
}
/* Container */
.media-default-skin--audio {
--media-default-accent-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-text-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-border-color: oklch(0 0 0 / 0.1);
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-surface-background-color: light-dark(oklch(1 0 0 / 0.5), oklch(0 0 0 / 0.4));
--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 (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-surface-inner-border-color: light-dark(oklch(1 0 0 / 0.1), oklch(1 0 0 / 0.2));
--media-surface-outer-border-color: light-dark(oklch(0 0 0 / 0.05), transparent);
}
}
/* Error Dialog */
.media-default-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: calc(var(--media-spacing) * 3);
align-items: center;
padding-inline: calc(var(--media-spacing) * 5) calc(var(--media-spacing) * 0.5);
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: calc(var(--media-spacing) * 2);
align-items: center;
}
/* Controls */
.media-default-skin--audio .media-controls {
--media-base-boundary-offset: 2;
color: var(--media-text-color);
.media-time-controls {
padding-inline: calc(var(--media-spacing) * 3);
}
.media-button--seek {
@container media-root (width < 32rem) {
display: none;
}
}
}
/* 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: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.2));
box-shadow: 0 0 0 1px light-dark(transparent, oklch(0 0 0 / 0.05));
}
.media-default-skin--audio .media-slider .media-slider__preview .media-slider__value {
bottom: calc(var(--media-spacing) * 10);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/live-audio.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/media/mux-audio.js"></script>
<link rel="stylesheet" href="./player.css">
<live-audio-player>
<media-container class="media-default-skin media-default-skin--audio">
<mux-audio src="https://stream.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM.m3u8"></mux-audio>
<media-error-dialog>
<media-dialog-backdrop class="media-overlay media-error-backdrop"></media-dialog-backdrop>
<media-dialog-popup class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-dialog-title class="media-error__title"></media-dialog-title>
<media-dialog-description class="media-error__description"></media-dialog-description>
</div>
<div class="media-error__actions">
<media-dialog-close class="media-button media-button--subtle"></media-dialog-close>
</div>
</div>
</media-dialog-popup>
</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-live-button class="media-button media-button--subtle media-button--live"></media-live-button>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
<div class="media-button-group">
<media-mute-button commandfor="live-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="live-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="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
</media-container>
</live-audio-player>/* -------------------------------------------------------------------------- */
/* Global styles for the host document, outside of the Shadow DOM */
/* -------------------------------------------------------------------------- */
video-player,
live-video-player,
media-i18n,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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.
*/
media-container video,
media-container [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
media-container 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,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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 the volume popover when its slider is unavailable. */
.media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-default-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 1.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius, 1.75rem);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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 1px 0 0 var(--media-surface-inner-border-color),
inset 0 0 0 1px oklch(from var(--media-surface-inner-border-color) l c h / calc(alpha * 0.5));
}
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-default-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-default-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * var(--media-base-boundary-offset, 2));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
border-radius: calc(Infinity * 1px);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-time-controls {
display: flex;
flex: 1;
gap: calc(var(--media-spacing) * 2.5);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row-reverse;
}
> .media-time:last-child {
@container media-time-controls (width < 16rem) {
display: none;
}
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
}
/* Base button */
.media-default-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
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, color, outline-offset, scale;
/* Fix weird jumping when clicking on the buttons in Safari. */
will-change: scale;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
/* Primary button variant */
.media-default-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-default-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-default-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-default-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-default-skin .media-button {
scale: 1;
transition-property: background-color, color;
will-change: auto;
}
}
.media-default-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-default-skin .media-badge {
padding: calc(var(--media-spacing) * 0.5) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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);
}
.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,
.media-default-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-default-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.media-default-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.media-slider__track {
position: relative;
overflow: hidden;
user-select: none;
background-color: oklch(from currentColor l c h / 0.2);
border-radius: var(--media-track-border-radius);
isolation: isolate;
&[data-orientation="horizontal"] {
width: 100%;
height: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: inherit;
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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;
scale: 0.8;
translate: -50% -50%;
&[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: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
opacity: 1;
}
&::after {
position: absolute;
inset: -4px;
content: "";
border-radius: inherit;
box-shadow: 0 0 0 2px currentColor;
}
&:not(:focus-visible)::after {
opacity: 0;
scale: 0.5;
}
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (hover: hover) and (pointer: fine) {
&:hover {
outline-color: oklch(from currentColor l c h / 0.15);
outline-offset: 0;
}
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
&::after {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 36;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: var(--media-max-size);
height: calc(var(--media-spacing) * 1);
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: 50%;
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 9));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 10.5));
display: flex;
flex-direction: column;
align-items: center;
}
.media-slider__chapter-title {
min-width: 0;
max-width: var(--media-max-size);
padding-inline: calc(var(--media-spacing) * 6);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
width: calc(var(--media-spacing) * 1);
height: calc(var(--media-spacing) * 1);
pointer-events: none;
content: "";
background-color: currentColor;
border-radius: 100%;
box-shadow:
0 0 0 1px var(--media-shadow-current-color, oklch(0 0 0 / 0.15)),
0 1px 2px 0 oklch(0 0 0 / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail),
.media-slider__preview[data-pointing]:not([data-dragging])::before {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(0.5 * var(--media-internal-scale-unit));
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(--media-popup-translate-x-distance, 0), var(--media-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"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 3) 0;
border-radius: calc(Infinity * 1px);
&:has(media-volume-slider[data-hidden]) {
display: none;
}
}
.media-default-skin .media-tooltip {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
font-size: var(--media-font-size-base);
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: calc(var(--media-spacing) * 1);
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: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.3);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-default-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 3);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
border-radius: var(--media-menu-border-radius);
@media (prefers-reduced-motion: reduce) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin-block: calc(var(--media-spacing) * 1);
border-bottom: 1px solid oklch(0 0 0 / 0.1);
box-shadow: 0 1px 0 0 oklch(1 0 0 / 0.075);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.65);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
opacity: 0;
.media-icon {
filter: drop-shadow(0 1px 0 var(--media-shadow-current-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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size 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 shared
by the HTML and React skins.
========================================================================== */
/* --- 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;
}
/* Container */
.media-default-skin--audio {
--media-default-accent-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-text-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-border-color: oklch(0 0 0 / 0.1);
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-surface-background-color: light-dark(oklch(1 0 0 / 0.5), oklch(0 0 0 / 0.4));
--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 (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-surface-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-surface-inner-border-color: light-dark(oklch(1 0 0 / 0.1), oklch(1 0 0 / 0.2));
--media-surface-outer-border-color: light-dark(oklch(0 0 0 / 0.05), transparent);
}
}
/* Error Dialog */
.media-default-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: calc(var(--media-spacing) * 3);
align-items: center;
padding-inline: calc(var(--media-spacing) * 5) calc(var(--media-spacing) * 0.5);
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: calc(var(--media-spacing) * 2);
align-items: center;
}
/* Controls */
.media-default-skin--audio .media-controls {
--media-base-boundary-offset: 2;
color: var(--media-text-color);
.media-time-controls {
padding-inline: calc(var(--media-spacing) * 3);
}
.media-button--seek {
@container media-root (width < 32rem) {
display: none;
}
}
}
/* 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: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.2));
box-shadow: 0 0 0 1px light-dark(transparent, oklch(0 0 0 / 0.05));
}
.media-default-skin--audio .media-slider .media-slider__preview .media-slider__value {
bottom: calc(var(--media-spacing) * 10);
}
Minimal live video skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { captionsText } from '@videojs/core/i18n/text/menu';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, FullscreenEnterIcon, FullscreenExitIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, RestartIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { Poster, useTranslator, Container, usePlayer, AirPlayButton, BufferingIndicator, CaptionsButton, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, LiveButton, Menu, MuteButton, PiPButton, PlayButton, Popover, StatusAnnouncer, StatusIndicator, Tooltip, VolumeIndicator, VolumeSlider } from '@videojs/react';
import { HlsJsVideo } from '@videojs/react/media/hlsjs-video';
import './player.css';
import { Player } from './player';
const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;
const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;
function CaptionsTrigger(): ReactNode {
const t = useTranslator();
const captions = useCaptionsOptions();
if (!captions) return null;
const { disabled } = captions;
if (!captions.showMenu) {
return (
<Tooltip.Root side="top">
<Tooltip.Trigger
render={
<CaptionsButton className="media-button--captions" render={<Button />}>
<CaptionsOffIcon className="media-icon media-icon--captions-off" />
<CaptionsOnIcon className="media-icon media-icon--captions-on" />
</CaptionsButton>
}
/>
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
return (
<Menu.Root side="top" align="center">
<Menu.Trigger
disabled={disabled}
render={
<CaptionsButton className="media-button--captions" render={<Button />}>
<CaptionsOffIcon className="media-icon media-icon--captions-off" />
<CaptionsOnIcon className="media-icon media-icon--captions-on" />
</CaptionsButton>
}
/>
<Menu.Popup className="media-popover media-menu media-menu--captions">
<Menu.Content className="media-menu__content">
<Menu.RadioGroup
className="media-menu__group"
value={captions.value}
onValueChange={captions.setValue}
aria-label={t(captionsText)}
>
{captions.options.map((option) => (
<Menu.RadioItem
key={option.value}
className="media-menu__item"
value={option.value}
disabled={option.disabled}
>
<bdi dir="auto">{option.label}</bdi>
<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.Popup>
</Menu.Root>
);
}
// ================================================================
// Player
// ================================================================
export interface LiveVideoPlayerProps {
src: string;
poster?: string | undefined;
style?: CSSProperties;
className?: string;
renderPoster?: Poster.Props['render'];
}
/**
* @example
* ```tsx
* <LiveVideoPlayer
* src="https://stream.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM.m3u8"
* poster="https://image.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM/thumbnail.webp"
* />
* ```
*/
export function LiveVideoPlayer({ src, poster, className, renderPoster, style, ...rest }: LiveVideoPlayerProps): ReactNode {
return (
<Player poster={poster}>
<Container className={`media-minimal-skin media-minimal-skin--video ${className ?? ''}`} style={style} {...rest}>
<HlsJsVideo src={src} playsInline />
<Poster render={renderPoster} />
<BufferingIndicator
render={(props) => (
<div {...props} className="media-buffering-indicator">
<SpinnerIcon className="media-icon" />
</div>
)}
/>
<ErrorDialog.Root>
<ErrorDialog.Backdrop className="media-overlay media-error-backdrop" />
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title"></ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--primary"></ErrorDialog.Close>
</div>
</div>
</ErrorDialog.Popup>
</ErrorDialog.Root>
<Controls.Root>
<Controls.Backdrop className="media-overlay" />
<Controls.Content 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>
<LiveButton className="media-button media-button--subtle media-button--live" />
<VolumePopover />
</div>
<div className="media-time-controls" aria-hidden="true" />
<div className="media-button-group">
<CaptionsTrigger />
<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.Content>
</Controls.Root>
{/* 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="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
{/* Gestures */}
<Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
<Gesture type="tap" action="toggleControls" pointer="touch" />
<Gesture type="doubletap" action="toggleFullscreen" region="center" />
{/* Input Indicators */}
<StatusAnnouncer className="media-sr-only" />
<div className="media-input-indicator-overlay">
<VolumeIndicator.Root className="media-volume-indicator">
<VolumeIndicator.Fill className="media-volume-indicator__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-volume-indicator__progress" aria-hidden="true" />
<VolumeIndicator.Value className="media-volume-indicator__value" />
</VolumeIndicator.Fill>
</VolumeIndicator.Root>
<StatusIndicator.Root
actions={TOP_STATUS_ACTIONS}
className="media-status-indicator media-status-indicator--state"
>
<div className="media-status-indicator__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-status-indicator__value" />
</div>
</StatusIndicator.Root>
<StatusIndicator.Root
actions={CENTER_STATUS_ACTIONS}
className="media-status-indicator media-status-indicator--playback"
>
<PlayIcon className="media-icon media-icon--play" />
<PauseIcon className="media-icon media-icon--pause" />
</StatusIndicator.Root>
</div>
</Container>
</Player>
);
}
// ================================================================
// 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 volumeUnavailable = usePlayer((s) => s.volumeAvailability !== 'available');
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 (volumeUnavailable) {
return (
<Tooltip.Root side="top" delay={0} sticky>
<Tooltip.Trigger render={muteButton} />
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="right">
<Tooltip.Root side="top" delay={0} sticky>
<Tooltip.Trigger render={<Popover.Trigger render={muteButton} />} />
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<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>
);
}
import { createPlayer } from '@videojs/react';
import { liveVideoFeatures } from '@videojs/react/live-video';
export const { Player } = createPlayer({ features: liveVideoFeatures });
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-minimal-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 0.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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-container-border-radius);
}
.media-minimal-skin video {
border-radius: inherit;
}
.media-minimal-skin:fullscreen ::slotted(video),
.media-minimal-skin:fullscreen video {
object-fit: contain;
}
.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) calc(var(--media-spacing) * 30),
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-backdrop {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-minimal-skin .media-controls-backdrop[data-visible],
.media-minimal-skin .media-error-backdrop[data-open] {
opacity: 1;
}
.media-minimal-skin:has(.media-buffering-indicator[data-visible]) .media-controls-backdrop {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-minimal-skin .media-error-backdrop[data-open] {
backdrop-filter: blur(16px) saturate(1.2);
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-minimal-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-minimal-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * (var(--media-base-boundary-offset, 0) + 1));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 3);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row;
}
}
.media-minimal-skin .media-time-group {
display: flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
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;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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;
}
}
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9.5);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(var(--media-spacing) * 2);
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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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-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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-minimal-skin .media-button {
scale: 1;
transition-property: background-color;
will-change: auto;
}
}
.media-minimal-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-badge {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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(var(--media-spacing) * 1);
}
.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,
.media-minimal-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-minimal-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.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]) {
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-container-border-radius);
}
.media-minimal-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
.media-minimal-skin:fullscreen media-poster ::slotted(img),
.media-minimal-skin:fullscreen media-poster img,
.media-minimal-skin:fullscreen > img {
object-fit: contain;
}
.media-minimal-skin .media-thumbnail {
position: relative;
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: calc(var(--media-spacing) * 2);
&::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 {
position: relative;
display: block;
max-width: var(--media-thumbnail-max-width);
max-height: var(--media-thumbnail-max-height);
overflow: clip;
border-radius: inherit;
}
.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;
}
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
&:has(.media-thumbnail__image[data-loading]) {
width: var(--media-thumbnail-max-width);
max-width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
.media-thumbnail__image {
opacity: 0;
}
.media-thumbnail__spinner {
opacity: 1;
}
}
}
.media-minimal-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: var(--media-track-border-radius);
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.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: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: var(--media-track-border-radius);
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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%;
&[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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:focus-visible,
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 28;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: 100%;
height: calc(var(--media-spacing) * 1);
@container media-root (width > 32rem) {
--media-max-size-factor: 36;
}
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: var(--media-preview-left, var(--media-slider-pointer));
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: filter, opacity, scale;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 11));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 5));
display: flex;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 2);
justify-content: center;
}
.media-slider__chapter-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
z-index: 1;
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
&[data-orientation="horizontal"]::before {
top: 50%;
left: var(--media-slider-pointer);
width: 1px;
height: calc(var(--media-spacing) * 5);
}
&[data-orientation="vertical"]::before {
top: calc(100% - var(--media-slider-pointer));
left: 50%;
width: calc(var(--media-spacing) * 5);
height: 1px;
}
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail) {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(var(--media-spacing) * 2);
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--media-popup-translate-x-distance, 0), var(--media-popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
filter: blur(4px);
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2);
font-size: var(--media-font-size-base);
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 1);
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: calc(var(--media-spacing) * -1);
font-family: inherit;
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-minimal-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 2.5);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--media-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) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin: calc(var(--media-spacing) * 1) 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
.media-minimal-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: calc(var(--media-spacing) * -2);
&:has(.media-controls[data-visible]) {
--media-caption-track-y: calc(var(--media-spacing) * -18);
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
--media-caption-track-y: calc(var(--media-spacing) * -12);
}
}
}
.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);
}
.media-minimal-skin .media-input-indicator-overlay {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
overflow: hidden;
color: oklch(1 0 0);
pointer-events: none;
border-radius: inherit;
}
.media-minimal-skin {
.media-volume-indicator,
.media-status-indicator--state {
position: absolute;
inset-inline: 0;
top: 0;
display: flex;
justify-content: center;
padding-top: calc(var(--media-spacing) * 3);
padding-bottom: calc(var(--media-spacing) * 32);
color: inherit;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
pointer-events: none;
background-image: linear-gradient(
to bottom,
oklch(0 0 0 / 0.35),
oklch(0 0 0 / 0.2) calc(var(--media-spacing) * 12),
oklch(0 0 0 / 0)
);
transform-origin: top center;
transition-timing-function: ease-out;
transition-duration: 100ms;
.media-volume-indicator__content,
.media-status-indicator__content {
display: flex;
gap: calc(var(--media-spacing) * 2);
align-items: center;
justify-content: space-between;
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
}
.media-icon {
display: none;
flex-shrink: 0;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
.media-volume-indicator__value,
.media-status-indicator__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-volume-indicator__content,
.media-status-indicator__content {
background: var(--media-controls-background-color);
border-radius: calc(var(--media-spacing) * 2);
}
}
&[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);
}
}
&[data-ending-style] {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -100%;
}
}
}
.media-seek-indicator,
.media-status-indicator--playback {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: calc(var(--media-spacing) * 4);
text-align: center;
}
}
.media-minimal-skin .media-volume-indicator {
.media-volume-indicator__content {
width: min(80%, calc(var(--media-spacing) * 56));
transform: translateX(0);
}
.media-volume-indicator__progress {
position: relative;
width: 100%;
height: calc(var(--media-spacing) * 0.75);
background: oklch(from currentColor l c h / 0.2);
border-radius: calc(Infinity * 1px);
box-shadow: 0 1px 0 var(--media-shadow-subtle-current-color);
&::before {
position: absolute;
inset-block: 0;
left: 0;
width: var(--media-volume-fill, 0%);
content: "";
background: var(--media-internal-accent-color);
border-radius: inherit;
transition: width 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]:not([data-starting-style], [data-ending-style]) .media-volume-indicator__content,
&[data-max]:not([data-starting-style], [data-ending-style]) .media-volume-indicator__content {
transform: translateX(0.25px);
transition: transform 300ms linear(0, -24 20%, 16 40%, -8 60%, 4 80%, 1);
}
}
}
.media-minimal-skin .media-status-indicator--state {
&[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;
}
}
.media-minimal-skin .media-status-indicator--playback {
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
.media-icon {
grid-area: 1 / 1;
width: calc(var(--media-icon-size) * 2);
height: calc(var(--media-icon-size) * 2);
opacity: 0;
scale: 0;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
opacity: 1;
scale: 1;
}
&[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;
&[data-starting-style],
&[data-ending-style],
.media-icon {
scale: 1;
}
.media-icon {
transition-duration: 50ms;
transition-property: opacity;
}
}
}
.media-minimal-skin .media-seek-indicator {
gap: calc(var(--media-spacing) * 1);
.media-seek-indicator__value {
font-variant-numeric: tabular-nums;
}
@container media-root (width > 24rem) {
padding: calc(var(--media-spacing) * 6);
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
.media-icon--seek {
display: block;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[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 shared
by the HTML and React skins.
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon,
.media-button--mute .media-icon,
.media-button--fullscreen .media-icon,
.media-button--pip .media-icon,
.media-button--cast .media-icon,
.media-button--airplay .media-icon,
.media-button--captions .media-icon {
opacity: 0;
}
.media-button--play .media-icon {
scale: 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 {
opacity: 1;
scale: 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;
}
@media (prefers-reduced-motion: reduce) {
.media-button--airplay {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
}
/* Container */
.media-minimal-skin--video {
--media-default-accent-color: oklch(1 0 0);
--media-border-color: light-dark(oklch(0 0 0 / 0.15), oklch(1 0 0 / 0.15));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-video-border-radius: var(--media-container-border-radius);
--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-popover-backdrop-filter: blur(16px) saturate(1.5);
--media-popover-background-color: oklch(0 0 0 / 0.5);
--media-popover-border-color: oklch(1 0 0 / 0.1);
--media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
--media-tooltip-background-color: var(--media-popover-background-color);
--media-tooltip-border-color: var(--media-popover-border-color);
--media-tooltip-text-color: currentColor;
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-error__dialog {
scale: 1;
transition-property: opacity;
}
}
@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);
}
&: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-container-border-radius: 0;
&::after {
display: none;
}
@media (width >= 1280px) {
--media-scale: 1.25;
}
@media (width >= 1536px) {
--media-scale: 1.5;
}
@media (width >= 1920px) {
--media-scale: 1.75;
}
}
* {
--media-focus-ring-color: oklch(1 0 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: calc(var(--media-spacing) * 3);
width: 100%;
max-width: calc(var(--media-spacing) * 64);
padding: calc(var(--media-spacing) * 4);
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__content {
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 2);
padding: calc(var(--media-spacing) * 1.5) 0;
}
.media-minimal-skin--video .media-error__title {
font-size: var(--media-font-size-medium);
}
.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.95;
}
.media-minimal-skin--video .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
/* Controls (hide/show behavior) */
.media-minimal-skin--video .media-controls {
--media-base-side-offset: 5;
--media-base-boundary-offset: 1;
--media-inset-factor: 1;
--media-inset: calc(var(--media-spacing) * var(--media-inset-factor));
--media-volume-mask: linear-gradient(to right, transparent 10%, #000 25%, #000 100%);
position: absolute;
inset-inline: var(--media-inset);
bottom: var(--media-inset);
z-index: 10;
flex-wrap: wrap;
column-gap: calc(var(--media-spacing) * 2);
color: oklch(1 0 0);
border-radius: calc(var(--media-spacing) * 3);
transition-timing-function: var(--media-controls-transition-timing-function);
/* Speed up the entry transition */
transition-duration: calc(var(--media-controls-transition-duration) / 2);
@media (pointer: fine) {
transition-property: translate, filter, opacity;
}
@media (pointer: coarse) {
transition-property: translate, opacity;
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 100%;
}
}
.media-time-controls {
--media-slider-height: calc(var(--media-spacing) * 5);
flex: 0 0 100%;
order: -1;
padding-inline: calc(var(--media-spacing) * 1.5);
}
.media-button-group:first-child {
flex: 1;
text-align: left;
}
.media-button-group:last-child {
flex: 1;
justify-content: end;
}
:is(.media-time-controls, .media-button-group:last-child) {
mask-image: var(--media-volume-mask-image, none);
mask-repeat: no-repeat;
mask-position: var(--media-volume-mask-position, 100% 0);
mask-size: var(--media-volume-mask-size, 200% 100%);
transition: mask-position 50ms ease-out;
}
&:has(.media-button--mute[aria-expanded="true"]) {
@container media-root (width <= 42rem) {
.media-button-group:last-child {
--media-volume-mask-image: var(--media-volume-mask);
--media-volume-mask-position: 0 0;
--media-volume-mask-size: 400% 100%;
}
}
@container media-root (width > 42rem) {
.media-time-controls {
--media-volume-mask-image: var(--media-volume-mask);
--media-volume-mask-position: 0 0;
}
}
}
@container media-root (width > 42rem) {
--media-inset-factor: 2;
--media-base-side-offset: 2;
flex-wrap: nowrap;
.media-time-controls {
--media-slider-height: calc(var(--media-spacing) * 8);
flex: 1;
order: unset;
}
.media-button-group:first-child,
.media-button-group:last-child {
flex: 0 0 auto;
}
}
}
.media-minimal-skin--video:has(.media-error[data-open]) .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-minimal-skin--video:has(.media-controls:not([data-visible])) {
cursor: none;
}
/* Popups & Animations */
.media-minimal-skin--video .media-popover--volume {
--media-popover-side-offset: 0rem;
padding: 0 calc(var(--media-spacing) * 3);
background: transparent;
}
/* Slider preview */
.media-minimal-skin--video .media-slider__value {
padding-inline: calc(var(--media-spacing) * 3);
text-shadow: 0 1px 0 var(--media-shadow-current-color);
}
.media-minimal-skin--video .media-slider .media-slider__preview {
--media-preview-end-inset: calc(100cqi - 100%);
--media-preview-left: clamp(
calc(var(--media-max-size) / 2),
var(--media-slider-pointer),
calc(100% - var(--media-max-size) / 2 + var(--media-preview-end-inset))
);
@container media-root (width > 42rem) {
--media-preview-left: var(--media-slider-pointer);
}
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/live-video-minimal.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/media/hlsjs-video.js"></script>
<link rel="stylesheet" href="./player.css">
<live-video-player poster="https://image.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM/thumbnail.webp">
<media-container class="media-minimal-skin media-minimal-skin--video">
<hlsjs-video src="https://stream.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM.m3u8" playsinline></hlsjs-video>
<media-poster>
<img alt="" decoding="async" />
</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>
<media-dialog-backdrop class="media-overlay media-error-backdrop"></media-dialog-backdrop>
<media-dialog-popup class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-dialog-title class="media-error__title"></media-dialog-title>
<media-dialog-description class="media-error__description"></media-dialog-description>
</div>
<div class="media-error__actions">
<media-dialog-close class="media-button media-button--primary"></media-dialog-close>
</div>
</div>
</media-dialog-popup>
</media-error-dialog>
<media-controls>
<media-controls-backdrop class="media-overlay"></media-controls-backdrop>
<media-controls-content 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-live-button class="media-button media-button--subtle media-button--live"></media-live-button>
<media-mute-button id="live-video-mute-trigger" commandfor="live-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-tooltip trigger="live-video-mute-trigger" delay="0" sticky side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-popover id="live-video-volume-popover" open-on-hover delay="200" close-delay="100" side="right" 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>
<div class="media-time-controls" aria-hidden="true"></div>
<div class="media-button-group">
<media-captions-button menu-for="captions-menu" commandfor="captions-tooltip" class="media-button media-button--subtle media-button--icon media-button--captions">
<media-icon name="captions-off" family="minimal" class="media-icon media-icon--captions-off"></media-icon>
<media-icon name="captions-on" family="minimal" class="media-icon media-icon--captions-on"></media-icon>
</media-captions-button>
<media-menu id="captions-menu" side="top" align="center" class="media-popover media-menu media-menu--captions">
<media-menu-content class="media-menu__content">
<media-captions-radio-group class="media-menu__group">
<template>
<media-menu-radio-item class="media-menu__item">
<bdi data-part="label" dir="auto"></bdi>
<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-content>
</media-menu>
<media-tooltip id="captions-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-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-content>
</media-controls>
<!-- 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="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></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="toggleFullscreen" region="center"></media-gesture>
<!-- Input Indicators -->
<media-status-announcer class="media-sr-only"></media-status-announcer>
<div class="media-input-indicator-overlay">
<media-volume-indicator hidden class="media-volume-indicator">
<media-volume-indicator-fill class="media-volume-indicator__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-volume-indicator__progress" aria-hidden="true"></div>
<media-volume-indicator-value class="media-volume-indicator__value"></media-volume-indicator-value>
</media-volume-indicator-fill>
</media-volume-indicator>
<media-status-indicator
hidden
actions="toggleSubtitles toggleFullscreen togglePictureInPicture"
class="media-status-indicator media-status-indicator--state"
>
<div class="media-status-indicator__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-status-indicator__value"></media-status-indicator-value>
</div>
</media-status-indicator>
<media-status-indicator hidden actions="togglePaused" class="media-status-indicator media-status-indicator--playback">
<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>
</live-video-player>/* -------------------------------------------------------------------------- */
/* Global styles for the host document, outside of the Shadow DOM */
/* -------------------------------------------------------------------------- */
video-player,
live-video-player,
media-i18n,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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.
*/
media-container video,
media-container [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
media-container 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,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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 the volume popover when its slider is unavailable. */
.media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-minimal-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 0.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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-container-border-radius);
}
.media-minimal-skin video {
border-radius: inherit;
}
.media-minimal-skin:fullscreen ::slotted(video),
.media-minimal-skin:fullscreen video {
object-fit: contain;
}
.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) calc(var(--media-spacing) * 30),
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-backdrop {
transition-delay: var(--media-error-dialog-transition-delay);
transition-duration: var(--media-error-dialog-transition-duration);
}
.media-minimal-skin .media-controls-backdrop[data-visible],
.media-minimal-skin .media-error-backdrop[data-open] {
opacity: 1;
}
.media-minimal-skin:has(.media-buffering-indicator[data-visible]) .media-controls-backdrop {
background: oklch(0 0 0 / 0.35);
opacity: 1;
backdrop-filter: blur(8px);
}
.media-minimal-skin .media-error-backdrop[data-open] {
backdrop-filter: blur(16px) saturate(1.2);
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-minimal-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-minimal-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * (var(--media-base-boundary-offset, 0) + 1));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 3);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row;
}
}
.media-minimal-skin .media-time-group {
display: flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
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;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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;
}
}
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9.5);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(var(--media-spacing) * 2);
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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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-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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-minimal-skin .media-button {
scale: 1;
transition-property: background-color;
will-change: auto;
}
}
.media-minimal-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-badge {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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(var(--media-spacing) * 1);
}
.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,
.media-minimal-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-minimal-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.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]) {
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-container-border-radius);
}
.media-minimal-skin > img {
object-fit: var(--media-object-fit, contain);
object-position: var(--media-object-position, center);
border-radius: inherit;
}
.media-minimal-skin:fullscreen media-poster ::slotted(img),
.media-minimal-skin:fullscreen media-poster img,
.media-minimal-skin:fullscreen > img {
object-fit: contain;
}
.media-minimal-skin .media-thumbnail {
position: relative;
pointer-events: none;
background-color: oklch(0 0 0 / 0.9);
border-radius: calc(var(--media-spacing) * 2);
&::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 {
position: relative;
display: block;
max-width: var(--media-thumbnail-max-width);
max-height: var(--media-thumbnail-max-height);
overflow: clip;
border-radius: inherit;
}
.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;
}
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
&:has(.media-thumbnail__image[data-loading]) {
width: var(--media-thumbnail-max-width);
max-width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
.media-thumbnail__image {
opacity: 0;
}
.media-thumbnail__spinner {
opacity: 1;
}
}
}
.media-minimal-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: var(--media-track-border-radius);
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.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: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: var(--media-track-border-radius);
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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%;
&[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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:focus-visible,
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 28;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: 100%;
height: calc(var(--media-spacing) * 1);
@container media-root (width > 32rem) {
--media-max-size-factor: 36;
}
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: var(--media-preview-left, var(--media-slider-pointer));
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: filter, opacity, scale;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 11));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 5));
display: flex;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 2);
justify-content: center;
}
.media-slider__chapter-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
z-index: 1;
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
&[data-orientation="horizontal"]::before {
top: 50%;
left: var(--media-slider-pointer);
width: 1px;
height: calc(var(--media-spacing) * 5);
}
&[data-orientation="vertical"]::before {
top: calc(100% - var(--media-slider-pointer));
left: 50%;
width: calc(var(--media-spacing) * 5);
height: 1px;
}
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail) {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(var(--media-spacing) * 2);
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--media-popup-translate-x-distance, 0), var(--media-popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
filter: blur(4px);
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2);
font-size: var(--media-font-size-base);
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 1);
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: calc(var(--media-spacing) * -1);
font-family: inherit;
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-minimal-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 2.5);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--media-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) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin: calc(var(--media-spacing) * 1) 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size on open/close. */
&[data-starting-style],
&[data-ending-style] {
transition: var(--media-popup-transition);
}
}
}
.media-minimal-skin {
--media-caption-track-duration: var(--media-controls-transition-duration);
--media-caption-track-delay: 25ms;
--media-caption-track-y: calc(var(--media-spacing) * -2);
&:has(.media-controls[data-visible]) {
--media-caption-track-y: calc(var(--media-spacing) * -18);
}
@container media-root (width > 42rem) {
&:has(.media-controls[data-visible]) > * {
--media-caption-track-y: calc(var(--media-spacing) * -12);
}
}
}
.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);
}
.media-minimal-skin .media-input-indicator-overlay {
position: absolute;
inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
justify-items: center;
overflow: hidden;
color: oklch(1 0 0);
pointer-events: none;
border-radius: inherit;
}
.media-minimal-skin {
.media-volume-indicator,
.media-status-indicator--state {
position: absolute;
inset-inline: 0;
top: 0;
display: flex;
justify-content: center;
padding-top: calc(var(--media-spacing) * 3);
padding-bottom: calc(var(--media-spacing) * 32);
color: inherit;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
pointer-events: none;
background-image: linear-gradient(
to bottom,
oklch(0 0 0 / 0.35),
oklch(0 0 0 / 0.2) calc(var(--media-spacing) * 12),
oklch(0 0 0 / 0)
);
transform-origin: top center;
transition-timing-function: ease-out;
transition-duration: 100ms;
.media-volume-indicator__content,
.media-status-indicator__content {
display: flex;
gap: calc(var(--media-spacing) * 2);
align-items: center;
justify-content: space-between;
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2.5);
}
.media-icon {
display: none;
flex-shrink: 0;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
.media-volume-indicator__value,
.media-status-indicator__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-volume-indicator__content,
.media-status-indicator__content {
background: var(--media-controls-background-color);
border-radius: calc(var(--media-spacing) * 2);
}
}
&[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);
}
}
&[data-ending-style] {
@media (prefers-reduced-motion: no-preference) {
translate: 0 -100%;
}
}
}
.media-seek-indicator,
.media-status-indicator--playback {
display: grid;
grid-row: 1;
grid-column: 2;
place-content: center;
padding: calc(var(--media-spacing) * 4);
text-align: center;
}
}
.media-minimal-skin .media-volume-indicator {
.media-volume-indicator__content {
width: min(80%, calc(var(--media-spacing) * 56));
transform: translateX(0);
}
.media-volume-indicator__progress {
position: relative;
width: 100%;
height: calc(var(--media-spacing) * 0.75);
background: oklch(from currentColor l c h / 0.2);
border-radius: calc(Infinity * 1px);
box-shadow: 0 1px 0 var(--media-shadow-subtle-current-color);
&::before {
position: absolute;
inset-block: 0;
left: 0;
width: var(--media-volume-fill, 0%);
content: "";
background: var(--media-internal-accent-color);
border-radius: inherit;
transition: width 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]:not([data-starting-style], [data-ending-style]) .media-volume-indicator__content,
&[data-max]:not([data-starting-style], [data-ending-style]) .media-volume-indicator__content {
transform: translateX(0.25px);
transition: transform 300ms linear(0, -24 20%, 16 40%, -8 60%, 4 80%, 1);
}
}
}
.media-minimal-skin .media-status-indicator--state {
&[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;
}
}
.media-minimal-skin .media-status-indicator--playback {
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
.media-icon {
grid-area: 1 / 1;
width: calc(var(--media-icon-size) * 2);
height: calc(var(--media-icon-size) * 2);
opacity: 0;
scale: 0;
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
&[data-status="pause"] .media-icon--pause,
&[data-status="play"] .media-icon--play {
opacity: 1;
scale: 1;
}
&[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;
&[data-starting-style],
&[data-ending-style],
.media-icon {
scale: 1;
}
.media-icon {
transition-duration: 50ms;
transition-property: opacity;
}
}
}
.media-minimal-skin .media-seek-indicator {
gap: calc(var(--media-spacing) * 1);
.media-seek-indicator__value {
font-variant-numeric: tabular-nums;
}
@container media-root (width > 24rem) {
padding: calc(var(--media-spacing) * 6);
}
&[data-direction="backward"] {
grid-column: 1;
justify-self: left;
}
&[data-direction="forward"] {
grid-column: 3;
justify-self: right;
}
.media-icon--seek {
display: block;
width: calc(var(--media-icon-size) * 1.5);
height: calc(var(--media-icon-size) * 1.5);
}
&[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 shared
by the HTML and React skins.
========================================================================== */
/* --- All icons hidden by default --- */
.media-button--play .media-icon,
.media-button--mute .media-icon,
.media-button--fullscreen .media-icon,
.media-button--pip .media-icon,
.media-button--cast .media-icon,
.media-button--airplay .media-icon,
.media-button--captions .media-icon {
opacity: 0;
}
.media-button--play .media-icon {
scale: 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 {
opacity: 1;
scale: 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;
}
@media (prefers-reduced-motion: reduce) {
.media-button--airplay {
--media-icon--airplay__fill-animation: none;
--media-icon--airplay__triangle-animation: none;
}
}
/* Container */
.media-minimal-skin--video {
--media-default-accent-color: oklch(1 0 0);
--media-border-color: light-dark(oklch(0 0 0 / 0.15), oklch(1 0 0 / 0.15));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-video-border-radius: var(--media-container-border-radius);
--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-popover-backdrop-filter: blur(16px) saturate(1.5);
--media-popover-background-color: oklch(0 0 0 / 0.5);
--media-popover-border-color: oklch(1 0 0 / 0.1);
--media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
--media-tooltip-background-color: var(--media-popover-background-color);
--media-tooltip-border-color: var(--media-popover-border-color);
--media-tooltip-text-color: currentColor;
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-error__dialog {
scale: 1;
transition-property: opacity;
}
}
@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);
}
&: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-container-border-radius: 0;
&::after {
display: none;
}
@media (width >= 1280px) {
--media-scale: 1.25;
}
@media (width >= 1536px) {
--media-scale: 1.5;
}
@media (width >= 1920px) {
--media-scale: 1.75;
}
}
* {
--media-focus-ring-color: oklch(1 0 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: calc(var(--media-spacing) * 3);
width: 100%;
max-width: calc(var(--media-spacing) * 64);
padding: calc(var(--media-spacing) * 4);
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__content {
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 2);
padding: calc(var(--media-spacing) * 1.5) 0;
}
.media-minimal-skin--video .media-error__title {
font-size: var(--media-font-size-medium);
}
.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.95;
}
.media-minimal-skin--video .media-error[data-ending-style] .media-error__dialog {
transition-delay: 0ms;
}
/* Controls (hide/show behavior) */
.media-minimal-skin--video .media-controls {
--media-base-side-offset: 5;
--media-base-boundary-offset: 1;
--media-inset-factor: 1;
--media-inset: calc(var(--media-spacing) * var(--media-inset-factor));
--media-volume-mask: linear-gradient(to right, transparent 10%, #000 25%, #000 100%);
position: absolute;
inset-inline: var(--media-inset);
bottom: var(--media-inset);
z-index: 10;
flex-wrap: wrap;
column-gap: calc(var(--media-spacing) * 2);
color: oklch(1 0 0);
border-radius: calc(var(--media-spacing) * 3);
transition-timing-function: var(--media-controls-transition-timing-function);
/* Speed up the entry transition */
transition-duration: calc(var(--media-controls-transition-duration) / 2);
@media (pointer: fine) {
transition-property: translate, filter, opacity;
}
@media (pointer: coarse) {
transition-property: translate, opacity;
}
&:not([data-visible]) {
pointer-events: none;
opacity: 0;
transition-duration: var(--media-controls-transition-duration);
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
filter: blur(8px);
}
@media (prefers-reduced-motion: no-preference) {
translate: 0 100%;
}
}
.media-time-controls {
--media-slider-height: calc(var(--media-spacing) * 5);
flex: 0 0 100%;
order: -1;
padding-inline: calc(var(--media-spacing) * 1.5);
}
.media-button-group:first-child {
flex: 1;
text-align: left;
}
.media-button-group:last-child {
flex: 1;
justify-content: end;
}
:is(.media-time-controls, .media-button-group:last-child) {
mask-image: var(--media-volume-mask-image, none);
mask-repeat: no-repeat;
mask-position: var(--media-volume-mask-position, 100% 0);
mask-size: var(--media-volume-mask-size, 200% 100%);
transition: mask-position 50ms ease-out;
}
&:has(.media-button--mute[aria-expanded="true"]) {
@container media-root (width <= 42rem) {
.media-button-group:last-child {
--media-volume-mask-image: var(--media-volume-mask);
--media-volume-mask-position: 0 0;
--media-volume-mask-size: 400% 100%;
}
}
@container media-root (width > 42rem) {
.media-time-controls {
--media-volume-mask-image: var(--media-volume-mask);
--media-volume-mask-position: 0 0;
}
}
}
@container media-root (width > 42rem) {
--media-inset-factor: 2;
--media-base-side-offset: 2;
flex-wrap: nowrap;
.media-time-controls {
--media-slider-height: calc(var(--media-spacing) * 8);
flex: 1;
order: unset;
}
.media-button-group:first-child,
.media-button-group:last-child {
flex: 0 0 auto;
}
}
}
.media-minimal-skin--video:has(.media-error[data-open]) .media-controls {
display: none;
}
/* Hide cursor when controls are hidden */
.media-minimal-skin--video:has(.media-controls:not([data-visible])) {
cursor: none;
}
/* Popups & Animations */
.media-minimal-skin--video .media-popover--volume {
--media-popover-side-offset: 0rem;
padding: 0 calc(var(--media-spacing) * 3);
background: transparent;
}
/* Slider preview */
.media-minimal-skin--video .media-slider__value {
padding-inline: calc(var(--media-spacing) * 3);
text-shadow: 0 1px 0 var(--media-shadow-current-color);
}
.media-minimal-skin--video .media-slider .media-slider__preview {
--media-preview-end-inset: calc(100cqi - 100%);
--media-preview-left: clamp(
calc(var(--media-max-size) / 2),
var(--media-slider-pointer),
calc(100% - var(--media-max-size) / 2 + var(--media-preview-end-inset))
);
@container media-root (width > 42rem) {
--media-preview-left: var(--media-slider-pointer);
}
}
Minimal live audio skin
'use client';
import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { PauseIcon, PlayIcon, RestartIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { Container, usePlayer, BufferingIndicator, ErrorDialog, Hotkey, LiveButton, MuteButton, PlayButton, Popover, StatusAnnouncer, Tooltip, VolumeSlider, type Poster } from '@videojs/react';
import { MuxAudio } from '@videojs/react/media/mux-audio';
import './player.css';
import { Player } from './player';
// ================================================================
// Player
// ================================================================
export interface LiveAudioPlayerProps {
src: string;
style?: CSSProperties;
className?: string;
}
/**
* @example
* ```tsx
* <LiveAudioPlayer
* src="https://stream.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM.m3u8"
* />
* ```
*/
export function LiveAudioPlayer({ src, className, ...rest }: LiveAudioPlayerProps): ReactNode {
return (
<Player>
<Container className={`media-minimal-skin media-minimal-skin--audio ${className ?? ''}`} {...rest}>
<MuxAudio src={src} />
<ErrorDialog.Root>
<ErrorDialog.Backdrop className="media-overlay media-error-backdrop" />
<ErrorDialog.Popup className="media-error">
<div className="media-error__dialog">
<div className="media-error__content">
<ErrorDialog.Title className="media-error__title"></ErrorDialog.Title>
<ErrorDialog.Description className="media-error__description" />
</div>
<div className="media-error__actions">
<ErrorDialog.Close className="media-button media-button--subtle"></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>
<LiveButton className="media-button media-button--subtle media-button--live" />
</div>
<div className="media-time-controls" aria-hidden="true" />
<div className="media-button-group">
<VolumePopover />
</div>
</Tooltip.Provider>
</div>
{/* Hotkeys */}
<Hotkey keys="Space" action="togglePaused" />
<Hotkey keys="k" action="togglePaused" />
<Hotkey keys="m" action="toggleMuted" />
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
{/* Input Feedback */}
<StatusAnnouncer className="media-sr-only" />
</Container>
</Player>
);
}
// ================================================================
// 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 volumeUnavailable = usePlayer((s) => s.volumeAvailability !== 'available');
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 (volumeUnavailable) {
return (
<Tooltip.Root side="top" delay={0} sticky>
<Tooltip.Trigger render={muteButton} />
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
);
}
return (
<Popover.Root openOnHover delay={200} closeDelay={100} side="left" boundary="viewport">
<Tooltip.Root side="top" delay={0} sticky>
<Tooltip.Trigger render={<Popover.Trigger render={muteButton} />} />
<Tooltip.Popup className="media-tooltip">
<Tooltip.Label />
<Tooltip.Shortcut className="media-tooltip__kbd" />
</Tooltip.Popup>
</Tooltip.Root>
<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>
);
}
import { createPlayer } from '@videojs/react';
import { liveAudioFeatures } from '@videojs/react/live-audio';
export const { Player } = createPlayer({ features: liveAudioFeatures });
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-minimal-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 0.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-minimal-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-minimal-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * (var(--media-base-boundary-offset, 0) + 1));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 3);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row;
}
}
.media-minimal-skin .media-time-group {
display: flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
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;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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;
}
}
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9.5);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(var(--media-spacing) * 2);
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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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-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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-minimal-skin .media-button {
scale: 1;
transition-property: background-color;
will-change: auto;
}
}
.media-minimal-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-badge {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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(var(--media-spacing) * 1);
}
.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,
.media-minimal-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-minimal-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.media-minimal-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: var(--media-track-border-radius);
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.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: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: var(--media-track-border-radius);
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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%;
&[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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:focus-visible,
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 28;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: 100%;
height: calc(var(--media-spacing) * 1);
@container media-root (width > 32rem) {
--media-max-size-factor: 36;
}
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: var(--media-preview-left, var(--media-slider-pointer));
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: filter, opacity, scale;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 11));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 5));
display: flex;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 2);
justify-content: center;
}
.media-slider__chapter-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
z-index: 1;
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
&[data-orientation="horizontal"]::before {
top: 50%;
left: var(--media-slider-pointer);
width: 1px;
height: calc(var(--media-spacing) * 5);
}
&[data-orientation="vertical"]::before {
top: calc(100% - var(--media-slider-pointer));
left: 50%;
width: calc(var(--media-spacing) * 5);
height: 1px;
}
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail) {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(var(--media-spacing) * 2);
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--media-popup-translate-x-distance, 0), var(--media-popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
filter: blur(4px);
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2);
font-size: var(--media-font-size-base);
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 1);
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: calc(var(--media-spacing) * -1);
font-family: inherit;
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-minimal-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 2.5);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--media-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) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin: calc(var(--media-spacing) * 1) 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size 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 shared
by the HTML and React skins.
========================================================================== */
/* --- 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;
}
/* Container */
.media-minimal-skin--audio {
--media-default-accent-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-controls-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-controls-backdrop-filter: blur(16px) saturate(1.5);
--media-controls-border-color: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.1));
--media-controls-text-color: light-dark(oklch(0 0 0), oklch(1 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-popover-backdrop-filter: blur(16px) saturate(1.5);
--media-popover-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-popover-border-color: oklch(0 0 0 / 0.1);
--media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
--media-tooltip-background-color: var(--media-popover-background-color);
--media-tooltip-border-color: var(--media-popover-border-color);
--media-tooltip-text-color: currentColor;
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-tooltip-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
}
}
/* Error Dialog */
.media-minimal-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: calc(var(--media-spacing) * 4);
align-items: center;
padding-inline: calc(var(--media-spacing) * 5) calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 2);
align-items: center;
}
/* Controls */
.media-minimal-skin--audio .media-controls {
--media-base-side-offset: 2;
--media-base-boundary-offset: 2;
gap: calc(var(--media-spacing) * 2);
color: var(--media-controls-text-color);
border-radius: var(--media-border-radius, calc(var(--media-spacing) * 3.5));
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 {
--media-popover-side-offset: 0rem;
padding: 0 calc(var(--media-spacing) * 2) 0 calc(var(--media-spacing) * 16);
background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%);
}
.media-minimal-skin--audio .media-slider .media-slider__preview .media-slider__value {
bottom: calc(var(--media-spacing) * 10);
}
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/live-audio-minimal.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html@10.0.0-beta.31/cdn/media/mux-audio.js"></script>
<link rel="stylesheet" href="./player.css">
<live-audio-player>
<media-container class="media-minimal-skin media-minimal-skin--audio">
<mux-audio src="https://stream.mux.com/v69RSHhFelSm4701snP22dYz2jICy4E4FUyk02rW4gxRM.m3u8"></mux-audio>
<media-error-dialog>
<media-dialog-backdrop class="media-overlay media-error-backdrop"></media-dialog-backdrop>
<media-dialog-popup class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-dialog-title class="media-error__title"></media-dialog-title>
<media-dialog-description class="media-error__description"></media-dialog-description>
</div>
<div class="media-error__actions">
<media-dialog-close class="media-button media-button--subtle"></media-dialog-close>
</div>
</div>
</media-dialog-popup>
</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-live-button class="media-button media-button--subtle media-button--live"></media-live-button>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
<div class="media-button-group">
<media-mute-button id="live-audio-mute-trigger" commandfor="live-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-tooltip trigger="live-audio-mute-trigger" delay="0" sticky side="top" class="media-tooltip">
<media-tooltip-label></media-tooltip-label>
<media-tooltip-shortcut class="media-tooltip__kbd"></media-tooltip-shortcut>
</media-tooltip>
<media-popover id="live-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>
<!-- 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="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
</media-container>
</live-audio-player>/* -------------------------------------------------------------------------- */
/* Global styles for the host document, outside of the Shadow DOM */
/* -------------------------------------------------------------------------- */
video-player,
live-video-player,
media-i18n,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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.
*/
media-container video,
media-container [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
media-container 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,
media-dialog,
media-alert-dialog,
media-error-dialog,
media-controls {
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 the volume popover when its slider is unavailable. */
.media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.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;
}
}
.media-minimal-skin {
/* Colors */
--media-internal-accent-color: var(--media-accent-color, var(--media-default-accent-color));
--media-accent-contrast-color: contrast-color(var(--media-internal-accent-color));
--media-accent-background-color: var(
--media-accent-color,
oklch(from var(--media-default-accent-color) l c h / calc(alpha * 0.1))
);
--media-internal-accent-text-color: var(
--media-accent-text-color,
contrast-color(var(--media-accent-color, oklch(0 0 0)))
);
--media-shadow-current-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.15));
--media-shadow-subtle-current-color: oklch(from var(--media-shadow-current-color) l c h / calc(alpha * 0.4));
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.3);
/* Scaling - used in video fullscreen to scale the UI. */
--media-scale: 1;
/* Fixed scaling unit to avoid inheriting the document's root font size. This must be a CSS <length>. */
--media-internal-scale-unit: var(--media-scale-unit, 16px);
--media-size: calc(var(--media-internal-scale-unit) * var(--media-scale));
/* Create a Tailwind-like spacing unit where 1 equals 4px with default scaling. */
--media-spacing: calc(var(--media-size) / 4);
/* Font sizes */
--media-font-size-medium: calc(0.9375 * var(--media-size)); /* 15px */
--media-font-size-base: calc(0.8125 * var(--media-size)); /* 13px */
--media-font-size-small: calc(0.6875 * var(--media-size)); /* 11px */
--media-font-size-tiny: calc(0.5625 * var(--media-size)); /* 9px */
--media-icon-size: calc(1.125 * var(--media-size)); /* 18px */
/* Radii */
--media-container-border-radius: var(--media-border-radius, 0.75rem);
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: var(--media-font-size-base);
-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-scrollbar-thumb-color) transparent;
scrollbar-width: thin;
border-radius: var(--media-container-border-radius);
isolation: isolate;
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-offset, outline-color;
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&::-webkit-scrollbar-thumb {
background: var(--media-scrollbar-thumb-color);
border-radius: 9999px;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-scrollbar-thumb-color: oklch(from currentColor l c h / 0.8);
scrollbar-width: auto;
}
}
.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;
}
@media (prefers-reduced-motion: reduce) {
--media-spinner-animation: none;
}
}
.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: calc(var(--media-spacing) * 2);
> * {
flex: 1;
}
}
.media-minimal-skin:has(.media-error[data-open]) .media-controls * {
visibility: hidden;
}
.media-minimal-skin .media-controls {
--media-popover-side-offset: calc(var(--media-spacing) * (var(--media-base-side-offset, 2) + 1));
--media-tooltip-side-offset: var(--media-popover-side-offset);
--media-popover-boundary-offset: calc(var(--media-spacing) * (var(--media-base-boundary-offset, 0) + 1));
--media-tooltip-boundary-offset: var(--media-popover-boundary-offset);
display: flex;
align-items: center;
padding: calc(var(--media-spacing) * 1);
container: media-controls / inline-size;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
background-color: var(--media-controls-background-color);
backdrop-filter: var(--media-controls-backdrop-filter);
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-time-controls {
display: flex;
flex: 1;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 3);
align-items: center;
container: media-time-controls / inline-size;
&:dir(rtl) {
flex-direction: row;
}
}
.media-minimal-skin .media-time-group {
display: flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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: calc(var(--media-spacing) * 1);
transition-timing-function: ease-out;
transition-duration: 100ms;
transition-property: outline-color, outline-offset;
@supports (corner-shape: squircle) {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
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;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.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;
}
}
/* Base button */
.media-minimal-skin .media-button {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: calc(var(--media-spacing) * 9.5);
min-height: 0;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 4);
text-align: center;
touch-action: manipulation;
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border: none;
border-radius: calc(var(--media-spacing) * 2);
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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
&[aria-disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
}
@supports (corner-shape: squircle) {
.media-minimal-skin .media-button {
border-radius: calc(var(--media-spacing) * 4);
corner-shape: squircle;
}
}
/* Primary button variant */
.media-minimal-skin .media-button--primary {
font-weight: 500;
color: var(--media-accent-contrast-color);
text-shadow: none;
background: var(--media-internal-accent-color);
}
/* Subtle button variant */
.media-minimal-skin .media-button--subtle {
color: inherit;
text-shadow: inherit;
background: transparent;
&:not([aria-disabled="true"]) {
&:hover,
&:focus-visible,
&[aria-expanded="true"] {
color: var(--media-internal-accent-text-color);
text-decoration: none;
background-color: var(--media-accent-background-color);
}
}
}
/* Icon button variant */
.media-minimal-skin .media-button--icon {
display: grid;
aspect-ratio: 1;
padding: 0;
&:active:not([aria-disabled="true"]) {
scale: 0.97;
}
.media-icon__container {
display: grid;
}
.media-icon {
grid-area: 1 / 1;
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, scale;
}
}
/* Seek button */
.media-minimal-skin .media-button--seek {
.media-icon__label {
position: absolute;
right: -1px;
bottom: -3px;
font-size: 0.715em; /* 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-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 {
.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);
}
}
/* 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: calc(var(--media-spacing) * 1.5);
align-items: center;
width: auto;
aspect-ratio: auto;
padding: calc(var(--media-spacing) * 2) calc(var(--media-spacing) * 3);
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.05em;
&::before {
display: inline-block;
flex-shrink: 0;
width: calc(var(--media-spacing) * 2);
height: calc(var(--media-spacing) * 2);
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);
}
}
@media (prefers-reduced-motion: reduce) {
.media-minimal-skin .media-button {
scale: 1;
transition-property: background-color;
will-change: auto;
}
}
.media-minimal-skin .media-button-group {
display: flex;
gap: 1px;
align-items: center;
&:dir(rtl) {
flex-direction: row-reverse;
}
}
.media-minimal-skin .media-badge {
padding: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 1.5);
font-size: var(--media-font-size-small);
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(var(--media-spacing) * 1);
}
.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,
.media-minimal-skin:dir(rtl) .media-menu__chevron {
scale: -1 1;
}
.media-minimal-skin:dir(rtl) .media-menu__chevron.media-icon--flipped {
scale: 1 1;
}
.media-minimal-skin .media-slider {
--media-track-size: calc(var(--media-spacing) * 1);
--media-track-highlighted-size: calc(var(--media-spacing) * 1.75);
--media-track-border-radius: 99px;
--media-thumb-size: calc(var(--media-spacing) * 3);
--media-chapter-gap: calc(var(--media-spacing) * 1);
--media-internal-chapter-inset-start: calc(var(--media-chapter-gap) / 2);
--media-internal-chapter-inset-end: calc(var(--media-chapter-gap) / 2);
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
border-radius: var(--media-track-border-radius);
&[data-orientation="horizontal"] {
width: var(--media-slider-width, 100%);
min-width: calc(var(--media-spacing) * 20);
height: var(--media-slider-height, calc(var(--media-spacing) * 8));
}
&[data-orientation="vertical"] {
width: var(--media-slider-width, calc(var(--media-spacing) * 8));
height: var(--media-slider-height, calc(var(--media-spacing) * 20));
}
/* Track */
.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: var(--media-track-size);
}
&[data-orientation="vertical"] {
width: var(--media-track-size);
height: 100%;
}
}
/* Shared track fills */
.media-slider__buffer,
.media-slider__fill {
position: absolute;
pointer-events: none;
border-radius: inherit;
&[data-orientation="horizontal"] {
inset-block: 0;
left: 0;
width: 100%;
}
&[data-orientation="vertical"] {
inset-inline: 0;
bottom: 0;
height: 100%;
}
@media (prefers-reduced-motion: no-preference) {
transition: clip-path 200ms ease-out;
}
}
&[data-dragging],
&[data-seeking] {
.media-slider__fill,
.media-slider__buffer {
transition-duration: 0ms;
}
}
/* Buffer */
.media-slider__buffer {
background-color: oklch(from currentColor l c h / 0.2);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-buffer)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-buffer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Fill */
.media-slider__fill {
background-color: var(--media-internal-accent-color);
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-fill)) 0 0 round var(--media-track-border-radius));
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-fill)) 0 0 0 round var(--media-track-border-radius));
}
}
&[data-dragging] {
.media-slider__fill[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-pointer)) 0 0 round var(--media-track-border-radius));
}
.media-slider__fill[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-pointer)) 0 0 0 round var(--media-track-border-radius));
}
}
/* Chapters */
.media-slider__chapters {
position: relative;
display: flex;
flex: 1;
align-items: center;
min-width: 0;
min-height: 0;
border-radius: inherit;
&[data-orientation="horizontal"] {
width: 100%;
height: 100%;
}
&[data-orientation="vertical"] {
flex-direction: column-reverse;
width: 100%;
height: 100%;
}
}
.media-slider__chapter {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
&:first-child {
--media-internal-chapter-inset-start: 0px;
}
&:last-child {
--media-internal-chapter-inset-end: 0px;
}
.media-slider__chapter-track {
border-radius: var(--media-track-border-radius);
@media (prefers-reduced-motion: no-preference) {
transition: 200ms ease-out;
transition-property: height, width;
}
}
&[data-orientation="horizontal"] {
clip-path: inset(0 calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start));
.media-slider__chapter-track {
height: var(--media-track-size);
clip-path: inset(
0 calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
height: var(--media-track-highlighted-size);
}
}
&[data-orientation="vertical"] {
clip-path: inset(calc(100% - var(--media-slider-chapter-end)) 0 var(--media-slider-chapter-start) 0);
.media-slider__chapter-track {
width: var(--media-track-size);
clip-path: inset(
calc(100% - var(--media-slider-chapter-end) + var(--media-internal-chapter-inset-end)) 0
calc(var(--media-slider-chapter-start) + var(--media-internal-chapter-inset-start)) 0 round
var(--media-track-border-radius)
);
}
&[data-highlighted] .media-slider__chapter-track {
width: var(--media-track-highlighted-size);
}
}
}
/* Thumb */
.media-slider__thumb {
position: absolute;
z-index: 10;
width: var(--media-thumb-size);
height: var(--media-thumb-size);
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-shadow-current-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%;
&[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: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:focus-visible,
&.media-slider__thumb--persistent {
opacity: 1;
scale: 1;
}
@media (prefers-reduced-motion: no-preference) {
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: opacity, outline-offset, left, top, scale;
}
}
@media (hover: hover) and (pointer: fine) {
&:hover .media-slider__thumb {
opacity: 1;
scale: 1;
}
}
&[data-dragging] .media-slider__thumb {
opacity: 1;
scale: 0.9;
@media (prefers-reduced-motion: no-preference) {
transition-property: opacity, outline-offset, scale;
}
&[data-orientation="horizontal"] {
left: var(--media-slider-pointer);
}
&[data-orientation="vertical"] {
top: calc(100% - var(--media-slider-pointer));
}
}
/* Preview */
.media-slider__preview {
--media-max-size-factor: 28;
--media-max-size: min(calc(var(--media-spacing) * var(--media-max-size-factor)), 100cqi);
min-width: 100%;
height: calc(var(--media-spacing) * 1);
@container media-root (width > 32rem) {
--media-max-size-factor: 36;
}
@container media-root (width > 42rem) {
--media-max-size-factor: 48;
}
.media-slider__thumbnail,
.media-slider__value {
position: absolute;
left: var(--media-preview-left, var(--media-slider-pointer));
max-width: var(--media-max-size);
opacity: 0;
filter: blur(8px);
transform-origin: bottom;
scale: 0.8;
translate: -50% calc(var(--media-spacing) * 2);
transition-timing-function: ease-out;
transition-duration: 150ms;
transition-property: filter, opacity, scale;
}
.media-slider__thumbnail {
--media-thumbnail-max-width: var(--media-max-size);
--media-thumbnail-max-height: var(--media-max-size);
bottom: calc(100% + (var(--media-spacing) * 11));
}
.media-slider__value {
bottom: calc(100% + (var(--media-spacing) * 5));
display: flex;
flex-direction: row-reverse;
gap: calc(var(--media-spacing) * 2);
justify-content: center;
}
.media-slider__chapter-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:empty {
display: none;
}
}
&::before {
position: absolute;
z-index: 1;
pointer-events: none;
content: "";
background-color: oklch(from currentColor l c h / 0.35);
opacity: 0;
scale: 0.5;
translate: -50% -50%;
transition-timing-function: ease-out;
transition-duration: 200ms;
transition-property: opacity, scale;
}
&[data-orientation="horizontal"]::before {
top: 50%;
left: var(--media-slider-pointer);
width: 1px;
height: calc(var(--media-spacing) * 5);
}
&[data-orientation="vertical"]::before {
top: calc(100% - var(--media-slider-pointer));
left: 50%;
width: calc(var(--media-spacing) * 5);
height: 1px;
}
&[data-pointing]:not([data-dragging])::before {
opacity: 1;
scale: 1;
}
}
&:is([data-pointing], :has(:focus-visible))
.media-slider__preview
:is(.media-slider__value, .media-slider__thumbnail) {
opacity: 1;
filter: blur(0);
scale: 1;
}
}
.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 {
--media-popup-translate-distance: calc(var(--media-spacing) * 2);
margin: 0;
overflow: visible;
color: inherit;
border: 0;
transition: var(--media-popup-transition);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
/* We have to use transform here for translate as the translate property is used for positioning by core. */
transform: translate(var(--media-popup-translate-x-distance, 0), var(--media-popup-translate-y-distance, 0));
scale: 0.95;
}
&[data-ending-style] {
filter: blur(4px);
transform: none;
/* Speed up the exit transition. */
transition-duration: max(0ms, calc(var(--media-popup-transition-duration) - 50ms));
}
&[data-side="top"] {
--media-popup-translate-y-distance: var(--media-popup-translate-distance);
transform-origin: bottom;
}
&[data-side="bottom"] {
--media-popup-translate-y-distance: calc(var(--media-popup-translate-distance) * -1);
transform-origin: top;
}
&[data-side="left"] {
--media-popup-translate-x-distance: var(--media-popup-translate-distance);
transform-origin: right;
}
&[data-side="right"] {
--media-popup-translate-x-distance: calc(var(--media-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: calc(var(--media-spacing) * 1) calc(var(--media-spacing) * 2);
font-size: var(--media-font-size-base);
color: var(--media-tooltip-text-color);
white-space: nowrap;
background-color: var(--media-tooltip-background-color);
border-radius: calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 1);
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: calc(var(--media-spacing) * -1);
font-family: inherit;
font-size: var(--media-font-size-small);
font-weight: 600;
line-height: 1.25;
text-align: center;
background-color: oklch(from currentColor l c h / 0.15);
border-radius: calc(var(--media-spacing) * 1);
}
}
.media-minimal-skin .media-popover--volume:has(media-volume-slider[data-hidden]) {
display: none;
}
.media-minimal-skin .media-menu {
--media-menu-transition-duration: 250ms;
--media-menu-max-height: calc(var(--media-spacing) * 56);
--media-menu-padding: calc(var(--media-spacing) * 1);
--media-menu-border-radius: calc(var(--media-spacing) * 2.5);
--media-menu-item-border-radius: calc(var(--media-menu-border-radius) - var(--media-menu-padding));
box-sizing: border-box;
min-width: max-content;
max-width: var(--media-popover-available-width, none);
max-height: min(var(--media-popover-available-height, var(--media-menu-max-height)), var(--media-menu-max-height));
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
background-color: var(--media-popover-background-color);
border-radius: var(--media-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) {
--media-menu-transition-duration: 0ms;
}
& > .media-menu__panel {
--media-menu-content-enter-translate: 100%;
position: absolute;
inset-inline: 0;
top: 0;
z-index: 10;
max-height: inherit;
padding: var(--media-menu-padding);
overflow: auto;
overscroll-behavior: none;
outline: none;
translate: 0 0;
transition-timing-function: ease-out;
transition-duration: var(--media-menu-transition-duration);
transition-property: translate, filter;
&:where([data-starting-style], [data-ending-style]) {
overflow: hidden;
pointer-events: none;
filter: blur(8px);
translate: var(--media-menu-content-enter-translate) 0;
}
&:dir(rtl):where([data-starting-style], [data-ending-style]) {
--media-menu-content-enter-translate: -100%;
}
}
.media-menu__separator {
margin: calc(var(--media-spacing) * 1) 0;
border-bottom: 1px solid oklch(1 0 0 / 0.1);
}
.media-menu__content,
.media-menu__group {
anchor-scope: --menu-item-highlight-anchor;
display: flex;
flex-direction: column;
gap: calc(var(--media-spacing) * 0.5);
@supports (top: anchor(top)) {
&::before {
position: absolute;
position-anchor: --menu-item-highlight-anchor;
inset: anchor(inside);
/* Firefox treats the moving highlight as a content shift and re-scrolls
the menu while hovering. Exclude it from scroll anchoring. */
overflow-anchor: none;
pointer-events: none;
content: "";
background-color: var(--media-accent-background-color);
border-radius: var(--media-menu-item-border-radius);
transition: inset 100ms ease-in-out;
}
&:has([data-highlighted=""])::before {
transition-duration: 0ms;
}
}
}
.media-menu__item,
.media-menu__back {
position: relative;
display: flex;
gap: calc(var(--media-spacing) * 1.5);
align-items: center;
padding: calc(var(--media-spacing) * 1.5) calc(var(--media-spacing) * 2);
text-align: start;
text-shadow: 0 1px 0 var(--media-shadow-current-color);
cursor: pointer;
user-select: none;
outline: 2px solid transparent;
outline-offset: -2px;
border-radius: var(--media-menu-item-border-radius);
transition: background-color, color;
transition-timing-function: ease-in-out;
transition-duration: 100ms;
.media-icon {
flex-shrink: 0;
color: oklch(from currentColor l c h / 0.5);
filter: drop-shadow(0 1px 0 var(--media-shadow-current-color));
}
&:focus-visible {
outline-color: var(--media-focus-ring-color);
outline-offset: 2px;
}
&:hover,
&[data-highlighted] {
color: var(--media-internal-accent-text-color);
background-color: var(--media-accent-background-color);
.media-icon {
color: inherit;
}
}
@supports (top: anchor(top)) {
transition-duration: 50ms;
&:hover,
&[data-highlighted] {
transition-duration: 200ms;
}
}
}
.media-menu__indicator {
flex-shrink: 0;
margin-inline: auto calc(var(--media-spacing) * -1);
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;
}
&[data-highlighted] {
@supports (top: anchor(top)) {
anchor-name: --menu-item-highlight-anchor;
background-color: transparent;
}
}
}
.media-menu__tier {
padding-inline-start: calc(var(--media-spacing) * 0.5);
padding-top: 1px;
font-size: var(--media-font-size-tiny);
font-weight: 600;
line-height: 1;
color: oklch(from currentColor l c h / 0.7);
}
.media-menu__back {
width: 100%;
margin-bottom: calc(var(--media-spacing) * 0.5);
}
.media-menu__hint {
display: inline-flex;
gap: calc(var(--media-spacing) * 1);
align-items: center;
min-width: 0;
padding-inline-start: calc(var(--media-spacing) * 2);
margin-inline-start: auto;
color: oklch(from currentColor l c h / 0.65);
}
.media-menu__hint-label {
max-width: calc(var(--media-spacing) * 24);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-menu__chevron {
width: calc(var(--media-spacing) * 3.5);
height: calc(var(--media-spacing) * 3.5);
}
/* Settings menu */
&.media-menu--settings {
width: var(--media-menu-width);
min-width: calc(var(--media-spacing) * 48);
height: var(--media-menu-height);
overflow: hidden;
/* Only the menu size changes between panels. */
transition:
var(--media-popup-transition),
width var(--media-popup-transition-timing-function) var(--media-menu-transition-duration),
height var(--media-popup-transition-timing-function) var(--media-menu-transition-duration);
& > .media-menu__content {
--media-menu-content-exit-translate: -100%;
translate: 0 0;
transition:
translate var(--media-menu-transition-duration) ease-out,
filter var(--media-menu-transition-duration) ease-out;
&:dir(rtl) {
--media-menu-content-exit-translate: 100%;
}
}
& > .media-menu__content[data-child-open] {
filter: blur(8px);
translate: var(--media-menu-content-exit-translate) 0;
}
/* WebKit can restart the parent Content transition while the
anchor-positioned highlight remains active. */
& > .media-menu__content[data-child-open]::before,
&:has(> .media-menu__panel[data-ending-style]) > .media-menu__content::before {
display: none;
}
/* Don't transition size 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 shared
by the HTML and React skins.
========================================================================== */
/* --- 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;
}
/* Container */
.media-minimal-skin--audio {
--media-default-accent-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-focus-ring-color: light-dark(oklch(0 0 0), oklch(1 0 0));
--media-controls-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-controls-backdrop-filter: blur(16px) saturate(1.5);
--media-controls-border-color: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.1));
--media-controls-text-color: light-dark(oklch(0 0 0), oklch(1 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-popover-backdrop-filter: blur(16px) saturate(1.5);
--media-popover-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
--media-popover-border-color: oklch(0 0 0 / 0.1);
--media-tooltip-backdrop-filter: var(--media-popover-backdrop-filter);
--media-tooltip-background-color: var(--media-popover-background-color);
--media-tooltip-border-color: var(--media-popover-border-color);
--media-tooltip-text-color: currentColor;
@media (prefers-reduced-motion: reduce) {
--media-error-dialog-transition-duration: 50ms;
--media-error-dialog-transition-delay: 0ms;
--media-popup-transition-duration: 0ms;
}
@media (prefers-reduced-transparency: reduce) or (prefers-contrast: more) {
--media-tooltip-background-color: light-dark(oklch(1 0 0), oklch(0 0 0));
}
}
/* Error Dialog */
.media-minimal-skin--audio .media-error__dialog {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
gap: calc(var(--media-spacing) * 4);
align-items: center;
padding-inline: calc(var(--media-spacing) * 5) calc(var(--media-spacing) * 2);
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: calc(var(--media-spacing) * 2);
align-items: center;
}
/* Controls */
.media-minimal-skin--audio .media-controls {
--media-base-side-offset: 2;
--media-base-boundary-offset: 2;
gap: calc(var(--media-spacing) * 2);
color: var(--media-controls-text-color);
border-radius: var(--media-border-radius, calc(var(--media-spacing) * 3.5));
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 {
--media-popover-side-offset: 0rem;
padding: 0 calc(var(--media-spacing) * 2) 0 calc(var(--media-spacing) * 16);
background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%);
}
.media-minimal-skin--audio .media-slider .media-slider__preview .media-slider__value {
bottom: calc(var(--media-spacing) * 10);
}