Skip to content
FrameworkStyle

Customize skins

Learn how to customize Video.js v10 skins by copying and modifying them

Video.js v10 comes with two pre-built skins; Default and Minimal. Basic customization is possible with CSS custom properties, but if you want more control over the design and functionality of your player, you can copy the skin’s code into your project and modify it as needed. We call this “ejecting” the skin, since you’re taking the internal code that makes up the skin and making it your own.

Basic customization

Property Name Description Type Example
--media-border-radius The border radius of the media player A valid border-radius value 1rem
--media-color-primary The color of icons and text in media controls <color> red

You can of course also add your own classnames to the skins themselves.

Ejecting

If you’d like to customize them you can fully customize them by “ejecting” the code and making it your own.

While eventually we’ll have a CLI that will eject skins in your preferred framework and style, for now we invite you to try it out with these copy-paste-ready implementations.

Default Video Skin

'use client';

import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode, isValidElement } from 'react';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { createPlayer, Poster, Container, usePlayer, AirPlayButton, BufferingIndicator, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, Popover, SeekButton, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider, type RenderProp } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './player.css';

const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;

const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;

function MenuChevron({ flipped = false }: { flipped?: boolean }): ReactNode {
  return <ChevronIcon className={`media-icon media-menu__chevron ${flipped ? 'media-icon--flipped' : undefined}`} />;
}

function SettingsMenu(): ReactNode {
  const playbackRate = usePlaybackRateOptions();
  const captions = useCaptionsOptions();
  const hasPlaybackRate = playbackRate?.state.availability === 'available';
  const hasCaptions = captions?.state.availability === 'available';

  if (!hasPlaybackRate && !hasCaptions) return null;

  return (
    <Menu.Root side="top" align="center">
      <Menu.Trigger aria-label="Settings" className="media-button--settings" render={<Button />}>
        <GearIcon className="media-icon media-icon--settings" />
      </Menu.Trigger>
      <Menu.Content className="media-surface media-popover media-menu media-menu--settings">
        <Menu.View className="media-menu__panel">
          <div className="media-menu__group">
            {hasPlaybackRate && playbackRate ? (
              <Menu.Root>
                <Menu.Trigger
                  type="playback-rate"
                  className="media-menu__item media-menu__item--submenu"
                  render={(props) => (
                    <div {...props}>
                      <span>Speed</span>
                      <span className="media-menu__hint">
                        <Menu.ItemValue className="media-menu__hint-label" />
                        <MenuChevron />
                      </span>
                    </div>
                  )}
                />
                <Menu.Content className="media-menu__panel">
                  <Menu.Back className="media-menu__back">
                    <MenuChevron flipped />
                    Speed
                  </Menu.Back>
                  <Menu.RadioGroup
                    className="media-menu__group"
                    value={playbackRate.value}
                    onValueChange={playbackRate.setValue}
                    aria-label="Playback rate"
                  >
                    {playbackRate.options.map((option) => (
                      <Menu.RadioItem
                        key={option.value}
                        className="media-menu__item"
                        value={option.value}
                        disabled={option.disabled}
                      >
                        <span>{option.label}</span>
                        <Menu.ItemIndicator
                          checked={option.value === playbackRate.value}
                          forceMount
                          className="media-menu__indicator"
                        >
                          <CheckIcon className="media-icon" />
                        </Menu.ItemIndicator>
                      </Menu.RadioItem>
                    ))}
                  </Menu.RadioGroup>
                </Menu.Content>
              </Menu.Root>
            ) : null}
            {hasCaptions && captions ? (
              <Menu.Root>
                <Menu.Trigger
                  type="captions"
                  className="media-menu__item media-menu__item--submenu"
                  render={(props) => (
                    <div {...props}>
                      <span>Captions</span>
                      <span className="media-menu__hint">
                        <Menu.ItemValue className="media-menu__hint-label" />
                        <MenuChevron />
                      </span>
                    </div>
                  )}
                />
                <Menu.Content className="media-menu__panel">
                  <Menu.Back className="media-menu__back">
                    <MenuChevron flipped />
                    Captions
                  </Menu.Back>
                  <Menu.RadioGroup
                    className="media-menu__group"
                    value={captions.value}
                    onValueChange={captions.setValue}
                    aria-label="Captions"
                  >
                    {captions.options.map((option) => (
                      <Menu.RadioItem
                        key={option.value}
                        className="media-menu__item"
                        value={option.value}
                        disabled={option.disabled}
                      >
                        <span>{option.label}</span>
                        <Menu.ItemIndicator
                          checked={option.value === captions.value}
                          forceMount
                          className="media-menu__indicator"
                        >
                          <CheckIcon className="media-icon" />
                        </Menu.ItemIndicator>
                      </Menu.RadioItem>
                    ))}
                  </Menu.RadioGroup>
                </Menu.Content>
              </Menu.Root>
            ) : null}
          </div>
        </Menu.View>
      </Menu.Content>
    </Menu.Root>
  );
}

// ================================================================
// Player
// ================================================================

const SEEK_TIME = 10;

export const Player = createPlayer({ features: videoFeatures });

export interface VideoPlayerProps {
  src: string;
  style?: CSSProperties;
  className?: string;
  poster?: string | RenderProp<Poster.State> | undefined;
}

/**
 * @example
 * ```tsx
 * <VideoPlayer
 *   src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
 *   poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp"
 * />
 * ```
 */
export function VideoPlayer({ src, className, poster, ...rest }: VideoPlayerProps): ReactNode {
  return (
    <Player.Provider>
      <Container className={`media-default-skin media-default-skin--video ${className ?? ''}`} {...rest}>
        <Video src={src} playsInline />

        {poster && (
          <Poster src={isString(poster) ? poster : undefined} render={isRenderProp(poster) ? poster : undefined} />
        )}

        <BufferingIndicator
          render={(props) => (
            <div {...props} className="media-buffering-indicator">
              <div className="media-surface">
                <SpinnerIcon className="media-icon" />
              </div>
            </div>
          )}
        />

        <ErrorDialog.Root>
          <ErrorDialog.Popup className="media-error">
            <div className="media-error__dialog media-surface">
              <div className="media-error__content">
                <ErrorDialog.Title className="media-error__title">Something went wrong.</ErrorDialog.Title>
                <ErrorDialog.Description className="media-error__description" />
              </div>
              <div className="media-error__actions">
                <ErrorDialog.Close className="media-button media-button--primary">OK</ErrorDialog.Close>
              </div>
            </div>
          </ErrorDialog.Popup>
        </ErrorDialog.Root>

        <Controls.Root className="media-surface media-controls">
          <Tooltip.Provider>
            <div className="media-button-group">
              <Tooltip.Root side="top">
                <Tooltip.Trigger
                  render={
                    <PlayButton className="media-button--play" render={<Button />}>
                      <RestartIcon className="media-icon media-icon--restart" />
                      <PlayIcon className="media-icon media-icon--play" />
                      <PauseIcon className="media-icon media-icon--pause" />
                    </PlayButton>
                  }
                />
                <Tooltip.Popup className="media-surface media-tooltip" />
              </Tooltip.Root>

              <Tooltip.Root side="top">
                <Tooltip.Trigger
                  render={
                    <SeekButton seconds={-SEEK_TIME} className="media-button--seek" render={<Button />}>
                      <span className="media-icon__container">
                        <SeekIcon className="media-icon media-icon--seek media-icon--flipped" />
                        <span className="media-icon__label">{SEEK_TIME}</span>
                      </span>
                    </SeekButton>
                  }
                />
                <Tooltip.Popup className="media-surface media-tooltip" />
              </Tooltip.Root>

              <Tooltip.Root side="top">
                <Tooltip.Trigger
                  render={
                    <SeekButton seconds={SEEK_TIME} className="media-button--seek" render={<Button />}>
                      <span className="media-icon__container">
                        <SeekIcon className="media-icon media-icon--seek" />
                        <span className="media-icon__label">{SEEK_TIME}</span>
                      </span>
                    </SeekButton>
                  }
                />
                <Tooltip.Popup className="media-surface media-tooltip" />
              </Tooltip.Root>
            </div>

            <div className="media-time-controls">
              <Time.Value type="current" className="media-time" />
              <TimeSlider.Root className="media-slider">
                <TimeSlider.Track className="media-slider__track">
                  <TimeSlider.Fill className="media-slider__fill" />
                  <TimeSlider.Buffer className="media-slider__buffer" />
                </TimeSlider.Track>
                <TimeSlider.Thumb className="media-slider__thumb" />

                <div className="media-surface media-thumbnail media-slider__thumbnail">
                  <Slider.Thumbnail className="media-thumbnail__image" />
                  <TimeSlider.Value type="pointer" className="media-time media-thumbnail__time" />
                  <SpinnerIcon className="media-thumbnail__spinner media-icon" />
                </div>
                <TimeSlider.Preview className="media-slider__preview">
                  <TimeSlider.Value type="pointer" className="media-time media-slider__value" />
                </TimeSlider.Preview>
              </TimeSlider.Root>
              <Time.Value type="duration" className="media-time" />
            </div>

            <div className="media-button-group">
              <VolumePopover />

              <SettingsMenu />

              <Tooltip.Root side="top">
                <Tooltip.Trigger
                  render={
                    <CastButton className="media-button--cast" render={<Button />}>
                      <CastEnterIcon className="media-icon media-icon--cast-enter" />
                      <CastExitIcon className="media-icon media-icon--cast-exit" />
                    </CastButton>
                  }
                />
                <Tooltip.Popup className="media-surface media-tooltip" />
              </Tooltip.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.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.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.Root>
            </div>
          </Tooltip.Provider>
        </Controls.Root>

        <div className="media-overlay" />

        {/* Hotkeys */}
        <Hotkey keys="Space" action="togglePaused" />
        <Hotkey keys="k" action="togglePaused" />
        <Hotkey keys="m" action="toggleMuted" />
        <Hotkey keys="f" action="toggleFullscreen" />
        <Hotkey keys="c" action="toggleSubtitles" />
        <Hotkey keys="i" action="togglePictureInPicture" />
        <Hotkey keys="ArrowRight" action="seekStep" value={SEEK_TIME / 2} />
        <Hotkey keys="ArrowLeft" action="seekStep" value={-(SEEK_TIME / 2)} />
        <Hotkey keys="l" action="seekStep" value={SEEK_TIME} />
        <Hotkey keys="j" action="seekStep" value={-SEEK_TIME} />
        <Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
        <Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
        <Hotkey keys="0-9" action="seekToPercent" />
        <Hotkey keys="Home" action="seekToPercent" value={0} />
        <Hotkey keys="End" action="seekToPercent" value={100} />
        <Hotkey keys=">" action="speedUp" />
        <Hotkey keys="<" action="speedDown" />

        {/* Gestures */}
        <Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
        <Gesture type="tap" action="toggleControls" pointer="touch" />
        <Gesture type="doubletap" action="seekStep" value={-SEEK_TIME} region="left" />
        <Gesture type="doubletap" action="toggleFullscreen" region="center" />
        <Gesture type="doubletap" action="seekStep" value={SEEK_TIME} region="right" />

        {/* Input Feedback */}
        <StatusAnnouncer />
        <div className="media-input-feedback">
          <VolumeIndicator.Root className="media-surface media-input-feedback-island media-input-feedback-island--volume">
            <VolumeIndicator.Fill className="media-input-feedback-island__content">
              <VolumeHighIcon className="media-icon media-icon--volume-high" />
              <VolumeLowIcon className="media-icon media-icon--volume-low" />
              <VolumeOffIcon className="media-icon media-icon--volume-off" />
              <VolumeIndicator.Value className="media-input-feedback-island__value" />
            </VolumeIndicator.Fill>
          </VolumeIndicator.Root>

          <StatusIndicator.Root
            actions={TOP_STATUS_ACTIONS}
            className="media-surface media-input-feedback-island media-input-feedback-island--status"
          >
            <div className="media-input-feedback-island__content">
              <CaptionsOnIcon className="media-icon media-icon--captions-on" />
              <CaptionsOffIcon className="media-icon media-icon--captions-off" />
              <FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" />
              <FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" />
              <PipEnterIcon className="media-icon media-icon--pip-enter" />
              <PipExitIcon className="media-icon media-icon--pip-exit" />
              <StatusIndicator.Value className="media-input-feedback-island__value" />
            </div>
          </StatusIndicator.Root>

          <SeekIndicator.Root className="media-input-feedback-bubble">
            <ChevronIcon className="media-icon media-icon--seek" />
            <SeekIndicator.Value className="media-time" />
          </SeekIndicator.Root>

          <StatusIndicator.Root actions={CENTER_STATUS_ACTIONS} className="media-input-feedback-bubble">
            <PlayIcon className="media-icon media-icon--play" />
            <PauseIcon className="media-icon media-icon--pause" />
          </StatusIndicator.Root>
        </div>
      </Container>

    </Player.Provider>
  );
}

// ================================================================
// Components
// ================================================================

const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
  return (
    <button
      ref={ref}
      type="button"
      className={`media-button media-button--subtle media-button--icon ${className ?? ''}`}
      {...props}
    />
  );
});

function VolumePopover(): ReactNode {
  const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');

  const muteButton = (
    <MuteButton className="media-button--mute" render={<Button />}>
      <VolumeOffIcon className="media-icon media-icon--volume-off" />
      <VolumeLowIcon className="media-icon media-icon--volume-low" />
      <VolumeHighIcon className="media-icon media-icon--volume-high" />
    </MuteButton>
  );

  if (volumeUnsupported) return muteButton;

  return (
    <Popover.Root openOnHover delay={200} closeDelay={100} side="top">
      <Popover.Trigger render={muteButton} />
      <Popover.Popup className="media-surface media-popover media-popover--volume">
        <VolumeSlider.Root className="media-slider" orientation="vertical" thumbAlignment="edge">
          <VolumeSlider.Track className="media-slider__track">
            <VolumeSlider.Fill className="media-slider__fill" />
          </VolumeSlider.Track>
          <VolumeSlider.Thumb className="media-slider__thumb media-slider__thumb--persistent" />
        </VolumeSlider.Root>
      </Popover.Popup>
    </Popover.Root>
  );
}

// ================================================================
// Utilities
// ================================================================

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isRenderProp(value: unknown): value is RenderProp<unknown> {
  return typeof value === 'function' || isValidElement(value);
}
'use client';

import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode, isValidElement } from 'react';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { createPlayer, Poster, Container, usePlayer, AirPlayButton, BufferingIndicator, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, Popover, SeekButton, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider, type RenderProp } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './player.css';

const bufferingIndicator = {
  root: "absolute inset-0 hidden items-center justify-center pointer-events-none text-white not-data-visible:[--media-spinner-animation:none] data-visible:flex",
  container: "p-1 rounded-full bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10",
};

const button = {
  base: "flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden",
  primary: "bg-white text-black font-medium text-shadow-none",
  subtle: "bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10",
  icon: "grid w-9 aspect-square p-0 active:scale-90",
  live: "inline-flex items-center gap-1.5 aspect-auto w-auto px-3 py-2 text-xs font-semibold uppercase tracking-wider leading-none before:inline-block before:size-2 before:shrink-0 before:rounded-full before:bg-current/40 before:transition-colors before:duration-150 before:ease-out before:content-[\"\"] data-[live-edge]:before:bg-red-500",
};

const buttonGroupEnd = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 justify-end @2xl/media-root:flex-none";

const buttonGroupStart = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 @2xl/media-root:flex-none";

const controls = "peer/controls @container/media-controls p-[0.375rem] flex items-center gap-x-[0.075rem] rounded-3xl text-shadow-2xs text-shadow-(color:--media-current-shadow-color) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 absolute bottom-2 inset-x-2 flex-wrap [color:var(--media-color-primary,oklch(1_0_0))] z-10 peer-data-open/error:hidden ease-(--media-controls-transition-timing-function) origin-bottom duration-(--media-controls-transition-duration) pointer-fine:will-change-[scale,filter,opacity] pointer-fine:transition-[scale,filter,opacity] pointer-coarse:will-change-[scale,opacity] pointer-coarse:transition-[scale,opacity] not-data-visible:pointer-events-none not-data-visible:opacity-0 motion-safe:not-data-visible:scale-90 pointer-fine:motion-safe:not-data-visible:blur-sm @2xl/media-root:bottom-3 @2xl/media-root:inset-x-3 @2xl/media-root:flex-nowrap @2xl/media-root:gap-x-0.5 @2xl/media-root:p-1";

const error = {
  root: "peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none",
  dialog: "flex flex-col gap-3 max-w-72 p-3 rounded-[1.75rem] text-white transition-[opacity,scale,transform] duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) ease-(--media-error-dialog-transition-timing-function) group-data-starting-style/error:opacity-0 group-data-starting-style/error:scale-50 group-data-ending-style/error:opacity-0 group-data-ending-style/error:scale-50 group-data-ending-style/error:delay-0 bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 text-shadow-2xs text-shadow-black/25",
  content: "flex flex-col gap-2 px-2 pt-2 pb-1.5 text-shadow-inherit",
  title: "font-semibold leading-tight text-base",
  description: "opacity-70 wrap-anywhere",
  actions: "flex gap-2 *:flex-1",
};

const icon = "block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out";

const iconContainer = "relative grid";

const iconFlipped = "[scale:-1_1]";

const iconState = {
  play: {
    button: "group",
    restart: "hidden opacity-0 group-data-ended:block group-data-ended:opacity-100",
    play: "hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100",
    pause: "hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100",
  },
  mute: {
    button: "group",
    volumeOff: "hidden opacity-0 group-data-muted:block group-data-muted:opacity-100",
    volumeLow: "hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100",
    volumeHigh: "hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100",
  },
  fullscreen: {
    button: "group",
    enter: "hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100",
    exit: "hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100",
  },
  captions: {
    button: "group",
    off: "hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100",
    on: "hidden opacity-0 group-data-active:block group-data-active:opacity-100",
  },
  pip: {
    button: "group",
    off: "hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100",
    on: "hidden opacity-0 group-data-pip:block group-data-pip:opacity-100",
  },
  cast: {
    button: "group",
    enter: "hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100",
    exit: "hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100",
  },
  airplay: {
    button: "group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]",
    enter: "hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100",
    exit: "hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100",
  },
};

const inputFeedback = {
  root: "absolute inset-x-0 top-0 bottom-14 pointer-events-none grid grid-cols-3 items-center justify-items-center @2xl/media-root:bottom-0 [color:var(--media-color-primary,oklch(1_0_0))]",
  island: {
    base: "group/input-indicator [--media-surface-background-color:oklch(0_0_0/0.25)] absolute top-3 rounded-full origin-top pointer-events-none text-inherit font-medium duration-100 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-250 data-starting-style:ease-in data-ending-style:duration-250 data-ending-style:ease-in pointer-coarse:will-change-[scale,translate,opacity] pointer-coarse:transition-[scale,translate,opacity] pointer-fine:motion-safe:will-change-[scale,translate,filter,opacity] pointer-fine:motion-safe:transition-[scale,translate,filter,opacity] pointer-fine:motion-safe:data-starting-style:blur-sm pointer-fine:motion-safe:data-starting-style:scale-90 pointer-fine:motion-safe:data-ending-style:blur-sm pointer-fine:motion-safe:data-ending-style:scale-90 motion-safe:data-ending-style:-translate-y-1/4 [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] contrast-more:[--media-surface-background-color:oklch(0_0_0)] bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10",
    content: "flex justify-between items-center gap-2 px-2.5 py-1 w-full **:mix-blend-difference",
    volume: "w-[min(80%,12rem)] *:[--media-progress-fill:var(--media-volume-fill)] *:rounded-[inherit] *:[background-image:linear-gradient(to_right,currentColor_0%,currentColor_var(--media-progress-fill),transparent_var(--media-progress-fill),transparent_100%)] *:[transition:--media-progress-fill_200ms_linear]",
    shownVolume: "data-open:duration-100 data-min:animate-media-shake data-max:animate-media-shake motion-reduce:data-min:animate-none motion-reduce:data-max:animate-none",
    shownStatus: "data-open:duration-100",
    icon: "hidden shrink-0",
    shownVolumeHigh: "group-data-[level=high]/input-indicator:block",
    shownVolumeLow: "group-data-[level=low]/input-indicator:block",
    shownVolumeOff: "group-data-[level=off]/input-indicator:block",
    shownCaptionsOn: "group-data-[status=captions-on]/input-indicator:block",
    shownCaptionsOff: "group-data-[status=captions-off]/input-indicator:block",
    shownFullscreenEnter: "group-data-[status=fullscreen]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=fullscreen]/input-indicator:animate-media-pop-in",
    shownFullscreenExit: "group-data-[status=exit-fullscreen]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=exit-fullscreen]/input-indicator:animate-media-pop-in",
    shownPipEnter: "group-data-[status=pip]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=pip]/input-indicator:animate-media-pop-in",
    shownPipExit: "group-data-[status=exit-pip]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=exit-pip]/input-indicator:animate-media-pop-in",
    value: "ml-auto",
  },
  bubble: {
    base: "group/input-indicator col-start-2 row-start-1 flex flex-col items-center justify-center p-4 transition-opacity duration-250 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-200 data-starting-style:ease-in data-ending-style:duration-200 data-ending-style:ease-in @2xl/media-root:p-8 not-data-direction:[transition-property:opacity,scale] not-data-direction:duration-600 not-data-direction:[transition-timing-function:ease-out,linear(0,0.12_1.5%,1.35_9.7%,2.2_13.9%,3_19.9%,2.7_21.8%,0.62_37.5%,0.96_50.9%,1)] motion-reduce:not-data-direction:transition-opacity motion-reduce:not-data-direction:duration-100 motion-reduce:not-data-direction:ease-out not-data-direction:data-starting-style:scale-80 not-data-direction:data-ending-style:scale-80 not-data-direction:data-starting-style:duration-200 not-data-direction:data-starting-style:ease-in not-data-direction:data-ending-style:duration-200 not-data-direction:data-ending-style:ease-in data-[direction=backward]:col-start-1 data-[direction=backward]:justify-self-start data-[direction=forward]:col-start-3 data-[direction=forward]:justify-self-end",
    icon: "hidden w-9 h-9",
    shownSeek: "group-data-direction/input-indicator:block group-data-[direction=backward]/input-indicator:-scale-x-100 group-not-data-starting-style/input-indicator:group-data-[direction=forward]/input-indicator:animate-media-slide-in-forward group-not-data-starting-style/input-indicator:group-data-[direction=backward]/input-indicator:animate-media-slide-in-backward motion-reduce:group-data-direction/input-indicator:animate-none",
    shownPause: "group-data-[status=pause]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=pause]/input-indicator:animate-media-pop-in",
    shownPlay: "group-data-[status=play]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=play]/input-indicator:animate-media-pop-in",
    time: "tabular-nums",
  },
};

const menu = {
  root: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:200ms] [--menu-item-transition-duration:100ms] motion-reduce:[--menu-transition-duration:0ms] motion-reduce:[--menu-item-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-(--media-popover-available-height,none) transition-[transform,scale,opacity,filter,width,height] duration-(--menu-transition-duration) ease-in-out box-border rounded-[1.25rem] p-1.5 overscroll-none min-w-24 overflow-auto",
  settings: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:200ms] [--menu-item-transition-duration:100ms] motion-reduce:[--menu-transition-duration:0ms] motion-reduce:[--menu-item-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-(--media-popover-available-height,none) transition-[transform,scale,opacity,filter,width,height] duration-(--menu-transition-duration) ease-in-out box-border rounded-[1.25rem] p-1.5 overscroll-none [--menu-transition-duration:250ms] relative min-w-44 w-(--media-menu-width) h-(--media-menu-height) overflow-hidden",
  group: "flex flex-col gap-0.5",
  item: "flex cursor-pointer select-none items-center rounded-full py-1.5 px-3 text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-[background-color,color] duration-(--menu-item-transition-duration) ease-out hover:bg-current/10 data-highlighted:bg-current/10 focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between gap-2 tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50",
  indicator: "-mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100 [&_.media-icon]:drop-shadow-[0_1px_0_var(--media-current-shadow-color)]",
  rootView: "absolute inset-0 overflow-auto overscroll-none p-1.5 outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate] data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur",
  submenuPanel: "absolute inset-0 overflow-auto overscroll-none p-1.5 outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full",
  back: "flex cursor-pointer select-none items-center rounded-full py-1.5 px-3 text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-[background-color,color] duration-(--menu-item-transition-duration) ease-out hover:bg-current/10 data-highlighted:bg-current/10 focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full gap-1.5 font-medium text-current/70 hover:text-inherit data-highlighted:text-inherit focus-visible:text-inherit",
  hint: "ml-auto flex min-w-0 items-center gap-1 text-xs text-current/65",
  hintLabel: "max-w-24 overflow-hidden text-ellipsis whitespace-nowrap",
  chevron: "size-3.5 first:-ml-1 last:-mr-1",
  settingsGroup: "group/settings",
  settingsTrigger: "group hidden group-has-[[data-availability=available]]/settings:grid",
  settingsIcon: "transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0",
};

const overlay = "absolute inset-0 flex flex-col items-start pointer-events-none rounded-[inherit] opacity-0 bg-linear-to-t from-black/50 via-black/30 via-25% to-transparent backdrop-blur-none backdrop-saturate-100 transition-[opacity,backdrop-filter] duration-(--media-controls-transition-duration) ease-out peer-data-visible/controls:opacity-100 peer-data-open/error:opacity-100 peer-data-open/error:duration-(--media-error-dialog-transition-duration) peer-data-open/error:delay-(--media-error-dialog-transition-delay) peer-data-open/error:backdrop-blur-lg peer-data-open/error:backdrop-saturate-150";

const popup = {
  popover: "bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset)",
  tooltip: "bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)",
  volume: "py-3 px-0 rounded-full",
};

const poster = () => "absolute inset-0 w-full h-full pointer-events-none transition-opacity duration-250 not-data-visible:opacity-0 rounded-[inherit] [object-fit:var(--media-object-fit,contain)] [object-position:var(--media-object-position,center)]";

const root = () => "**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,2rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] bg-black overflow-clip after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10 after:inset-0 after:ring-1 after:ring-inset after:ring-black/10 dark:after:ring-white/15 [&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&_video]:[object-fit:var(--media-object-fit,contain)] [&_video]:[object-position:var(--media-object-position,center)] [--media-spring-timing-function:linear(0,0.034_1.5%,0.763_9.7%,1.066_13.9%,1.198_19.9%,1.184_21.8%,0.963_37.5%,0.997_50.9%,1)] [--media-video-border-radius:var(--media-border-radius,2rem)] [--media-controls-transition-duration:100ms] [--media-controls-transition-timing-function:ease-out] [--media-error-dialog-transition-duration:350ms] [--media-error-dialog-transition-delay:100ms] [--media-error-dialog-transition-timing-function:var(--media-spring-timing-function)] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-surface-background-color:oklch(1_0_0/0.1)] [--media-surface-inner-border-color:oklch(1_0_0/0.05)] [--media-surface-outer-border-color:oklch(0_0_0/0.1)] [--media-surface-shadow-color:oklch(0_0_0/0.15)] [--media-surface-backdrop-filter:blur(16px)_saturate(1.5)] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-error-dialog-transition-timing-function:ease-out] [--media-tooltip-side-offset:0.75rem] [--media-tooltip-boundary-offset:0.5rem] [--media-popover-side-offset:0.5rem] [--media-popover-boundary-offset:0.5rem] motion-reduce:[--media-popup-transition-duration:0ms] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] contrast-more:[--media-surface-background-color:oklch(0_0_0)] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-inner-border-color:oklch(1_0_0/0.25)] contrast-more:[--media-surface-inner-border-color:oklch(1_0_0/0.25)] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:transparent] contrast-more:[--media-surface-outer-border-color:transparent] pointer-fine:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:300ms] pointer-coarse:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:150ms] motion-reduce:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:50ms] [--media-caption-track-y:-0.5rem] [--media-caption-track-delay:25ms] [--media-caption-track-duration:var(--media-controls-transition-duration)] has-[[data-controls][data-visible]]:[--media-caption-track-y:-5.5rem] @2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:-3.5rem] [&_video::-webkit-media-text-track-container]:transition-[translate] [&_video::-webkit-media-text-track-container]:duration-(--media-caption-track-duration) [&_video::-webkit-media-text-track-container]:ease-out [&_video::-webkit-media-text-track-container]:delay-(--media-caption-track-delay) [&_video::-webkit-media-text-track-container]:translate-y-(--media-caption-track-y) [&_video::-webkit-media-text-track-container]:scale-98 [&_video::-webkit-media-text-track-container]:z-1 [&_video::-webkit-media-text-track-container]:font-[inherit] [&:fullscreen]:[--media-border-radius:0] [&:fullscreen_video]:object-contain";

const seek = {
  label: "text-[10px] font-medium tracking-tighter tabular-nums",
  labelForward: "absolute -right-px -bottom-0.75",
  labelBackward: "absolute -left-px -bottom-0.75",
};

const slider = {
  root: "group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20",
  track: "relative isolate overflow-hidden rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1 data-[orientation=vertical]:h-full bg-white/20 ring-1 ring-black/5",
  fill: {
    base: "absolute rounded-[inherit] pointer-events-none",
    fill: "bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill)",
    buffer: "bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)",
  },
  thumb: {
    base: "z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.35),0_1px_2px_-1px_oklch(0_0_0/0.35)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/15 hover:outline-offset-0 focus-visible:outline-current/15 focus-visible:outline-offset-0 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill))]",
    persistent: "size-3",
    interactive: "size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3",
  },
  preview: "group/preview before:block before:min-w-1 before:h-1 before:bg-current before:rounded-full before:opacity-0 before:scale-50 before:shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_2px_0_oklch(0_0_0/0.35)] before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden",
  value: "absolute bottom-9 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none",
};

const thumbnail = {
  root: "group/thumbnail peer/thumbnail pointer-events-none bg-black/90 rounded-xl bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 [--media-slider-thumbnail-max-width:11rem] [--media-slider-thumbnail-padding:-1.125rem] [--media-slider-thumbnail-inset:calc((100cqi-100%)/2)] absolute [left:clamp(calc(var(--media-slider-thumbnail-max-width)/2+var(--media-slider-thumbnail-padding)-var(--media-slider-thumbnail-inset)),var(--media-slider-pointer),calc(100%-var(--media-slider-thumbnail-max-width)/2-var(--media-slider-thumbnail-padding)+var(--media-slider-thumbnail-inset)))] bottom-[calc(100%+1.2rem)] -translate-x-1/2 opacity-0 scale-80 blur-sm origin-bottom transition-[scale,opacity,filter] duration-150 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:opacity-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:scale-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:blur-none has-[[role=img][data-loading]]:max-h-24",
  image: "block relative overflow-clip rounded-[inherit] transition-opacity duration-150 ease-out after:absolute after:inset-0 after:rounded-[inherit] after:bg-linear-to-t after:from-black/80 after:via-black/30 after:to-black/0 data-loading:opacity-0 max-w-(--media-slider-thumbnail-max-width)",
  time: "absolute bottom-2 inset-x-0 text-center tabular-nums",
  spinner: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity duration-150 ease-out group-not-has-[[role=img][data-loading]]/thumbnail:[--media-spinner-animation:none] group-has-[[role=img][data-loading]]/thumbnail:opacity-100",
};

const time = {
  group: "@container/media-time flex items-center flex-1 gap-3 px-2 grow-0 shrink-0 basis-full order-[-1] px-2.5 @2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]",
  current: "hidden @2xs/media-time:block tabular-nums",
  duration: "tabular-nums",
};

interface VideoSkinProps {
  src: string;
  style?: CSSProperties;
  className?: string;
  poster?: string | RenderProp<Poster.State> | undefined;
}

const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;

const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;

function MenuChevron({ flipped = false }: { flipped?: boolean }): ReactNode {
  return <ChevronIcon className={`${icon} ${menu.chevron} ${flipped ? iconFlipped : undefined}`} />;
}

function SettingsMenu(): ReactNode {
  const playbackRate = usePlaybackRateOptions();
  const captions = useCaptionsOptions();
  const hasPlaybackRate = playbackRate?.state.availability === 'available';
  const hasCaptions = captions?.state.availability === 'available';

  if (!hasPlaybackRate && !hasCaptions) return null;

  return (
    <Menu.Root side="top" align="center">
      <Menu.Trigger
        aria-label="Settings"
        className="media-button--settings"
        render={<Button className={`${button.icon} ${menu.settingsTrigger}`} />}
      >
        <GearIcon className={`${icon} ${menu.settingsIcon}`} />
      </Menu.Trigger>
      <Menu.Content className={menu.settings}>
        <Menu.View className={menu.rootView}>
          <div className={menu.group}>
            {hasPlaybackRate && playbackRate ? (
              <Menu.Root>
                <Menu.Trigger
                  type="playback-rate"
                  className={`${menu.item} media-menu__item--submenu`}
                  render={(props) => (
                    <div {...props}>
                      <span>Speed</span>
                      <span className={menu.hint}>
                        <Menu.ItemValue className={menu.hintLabel} />
                        <MenuChevron />
                      </span>
                    </div>
                  )}
                />
                <Menu.Content className={menu.submenuPanel}>
                  <Menu.Back className={menu.back}>
                    <MenuChevron flipped />
                    Speed
                  </Menu.Back>
                  <Menu.RadioGroup
                    className={menu.group}
                    value={playbackRate.value}
                    onValueChange={playbackRate.setValue}
                    aria-label="Playback rate"
                  >
                    {playbackRate.options.map((option) => (
                      <Menu.RadioItem
                        key={option.value}
                        className={menu.item}
                        value={option.value}
                        disabled={option.disabled}
                      >
                        <span>{option.label}</span>
                        <Menu.ItemIndicator
                          checked={option.value === playbackRate.value}
                          forceMount
                          className={menu.indicator}
                        >
                          <CheckIcon className={icon} />
                        </Menu.ItemIndicator>
                      </Menu.RadioItem>
                    ))}
                  </Menu.RadioGroup>
                </Menu.Content>
              </Menu.Root>
            ) : null}
            {hasCaptions && captions ? (
              <Menu.Root>
                <Menu.Trigger
                  type="captions"
                  className={`${menu.item} media-menu__item--submenu`}
                  render={(props) => (
                    <div {...props}>
                      <span>Captions</span>
                      <span className={menu.hint}>
                        <Menu.ItemValue className={menu.hintLabel} />
                        <MenuChevron />
                      </span>
                    </div>
                  )}
                />
                <Menu.Content className={menu.submenuPanel}>
                  <Menu.Back className={menu.back}>
                    <MenuChevron flipped />
                    Captions
                  </Menu.Back>
                  <Menu.RadioGroup
                    className={menu.group}
                    value={captions.value}
                    onValueChange={captions.setValue}
                    aria-label="Captions"
                  >
                    {captions.options.map((option) => (
                      <Menu.RadioItem
                        key={option.value}
                        className={menu.item}
                        value={option.value}
                        disabled={option.disabled}
                      >
                        <span>{option.label}</span>
                        <Menu.ItemIndicator
                          checked={option.value === captions.value}
                          forceMount
                          className={menu.indicator}
                        >
                          <CheckIcon className={icon} />
                        </Menu.ItemIndicator>
                      </Menu.RadioItem>
                    ))}
                  </Menu.RadioGroup>
                </Menu.Content>
              </Menu.Root>
            ) : null}
          </div>
        </Menu.View>
      </Menu.Content>
    </Menu.Root>
  );
}

export function VideoSkinTailwind({ children, className, poster: posterProp, ...rest }: VideoSkinProps): ReactNode {

  return (
    <Container className={`${root(false)} ${className ?? ''}`} {...rest}>
      {children}

      {posterProp && (
        <Poster
          src={isString(posterProp) ? posterProp : undefined}
          render={isRenderProp(posterProp) ? posterProp : undefined}
          className={poster(false)}
        />
      )}

      <BufferingIndicator
        render={(props) => (
          <div {...props} className={bufferingIndicator.root}>
            <div className={bufferingIndicator.container}>
              <SpinnerIcon className={icon} />
            </div>
          </div>
        )}
      />

      <ErrorDialog.Root>
        <ErrorDialog.Popup className={error.root}>
          <div className={error.dialog}>
            <div className={error.content}>
              <ErrorDialog.Title className={error.title}>Something went wrong.</ErrorDialog.Title>
              <ErrorDialog.Description className={error.description} />
            </div>
            <div className={error.actions}>
              <ErrorDialog.Close className={`${button.base} ${button.primary}`}>OK</ErrorDialog.Close>
            </div>
          </div>
        </ErrorDialog.Popup>
      </ErrorDialog.Root>

      <Controls.Root
        data-controls="" // Used as a hook for Tailwind has-[] styles
        className={controls}
      >
        <Tooltip.Provider>
          <div className={buttonGroupStart}>
            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <PlayButton className={iconState.play.button} render={<Button />}>
                    <RestartIcon className={`${icon} ${iconState.play.restart}`} />
                    <PlayIcon className={`${icon} ${iconState.play.play}`} />
                    <PauseIcon className={`${icon} ${iconState.play.pause}`} />
                  </PlayButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <SeekButton seconds={-SEEK_TIME} render={<Button />}>
                    <span className={iconContainer}>
                      <SeekIcon className={`${icon} ${iconFlipped}`} />
                      <span className={`${seek.label} ${seek.labelBackward}`}>{SEEK_TIME}</span>
                    </span>
                  </SeekButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <SeekButton seconds={SEEK_TIME} render={<Button />}>
                    <span className={iconContainer}>
                      <SeekIcon className={icon} />
                      <span className={`${seek.label} ${seek.labelForward}`}>{SEEK_TIME}</span>
                    </span>
                  </SeekButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>
          </div>

          <div className={time.group}>
            <Time.Value type="current" className={time.current} />
            <TimeSlider.Root render={<SliderRoot />}>
              <TimeSlider.Track render={<SliderTrack />}>
                <TimeSlider.Fill render={<SliderFill />} />
                <TimeSlider.Buffer render={<SliderBuffer />} />
              </TimeSlider.Track>
              <TimeSlider.Thumb render={<SliderThumb />} />
              <div className={thumbnail.root}>
                <Slider.Thumbnail className={thumbnail.image} />
                <TimeSlider.Value type="pointer" className={thumbnail.time} />
                <SpinnerIcon className={`${icon} ${thumbnail.spinner}`} />
              </div>
              <TimeSlider.Preview className={slider.preview}>
                <TimeSlider.Value type="pointer" className={slider.value} />
              </TimeSlider.Preview>
            </TimeSlider.Root>
            <Time.Value type="duration" className={time.duration} />
          </div>

          <div className={buttonGroupEnd}>
            <VolumePopover />

            <SettingsMenu />

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <CastButton className={iconState.cast.button} render={<Button />}>
                    <CastEnterIcon className={`${icon} ${iconState.cast.enter}`} />
                    <CastExitIcon className={`${icon} ${iconState.cast.exit}`} />
                  </CastButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <AirPlayButton className={iconState.airplay.button} render={<Button />}>
                    <AirPlayEnterIcon className={`${icon} ${iconState.airplay.enter}`} />
                    <AirPlayExitIcon className={`${icon} ${iconState.airplay.exit}`} />
                  </AirPlayButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <PiPButton className={iconState.pip.button} render={<Button />}>
                    <PipEnterIcon className={`${icon} ${iconState.pip.off}`} />
                    <PipExitIcon className={`${icon} ${iconState.pip.on}`} />
                  </PiPButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <FullscreenButton className={iconState.fullscreen.button} render={<Button />}>
                    <FullscreenEnterIcon className={`${icon} ${iconState.fullscreen.enter}`} />
                    <FullscreenExitIcon className={`${icon} ${iconState.fullscreen.exit}`} />
                  </FullscreenButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>
          </div>
        </Tooltip.Provider>
      </Controls.Root>

      <div className={overlay} />

      {/* Hotkeys */}
      <Hotkey keys="Space" action="togglePaused" />
      <Hotkey keys="k" action="togglePaused" />
      <Hotkey keys="m" action="toggleMuted" />
      <Hotkey keys="f" action="toggleFullscreen" />
      <Hotkey keys="c" action="toggleSubtitles" />
      <Hotkey keys="i" action="togglePictureInPicture" />
      <Hotkey keys="ArrowRight" action="seekStep" value={SEEK_TIME / 2} />
      <Hotkey keys="ArrowLeft" action="seekStep" value={-(SEEK_TIME / 2)} />
      <Hotkey keys="l" action="seekStep" value={SEEK_TIME} />
      <Hotkey keys="j" action="seekStep" value={-SEEK_TIME} />
      <Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
      <Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
      <Hotkey keys="0-9" action="seekToPercent" />
      <Hotkey keys="Home" action="seekToPercent" value={0} />
      <Hotkey keys="End" action="seekToPercent" value={100} />
      <Hotkey keys=">" action="speedUp" />
      <Hotkey keys="<" action="speedDown" />

      {/* Gestures */}
      <Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
      <Gesture type="tap" action="toggleControls" pointer="touch" />
      <Gesture type="doubletap" action="seekStep" value={-SEEK_TIME} region="left" />
      <Gesture type="doubletap" action="toggleFullscreen" region="center" />
      <Gesture type="doubletap" action="seekStep" value={SEEK_TIME} region="right" />

      {/* Input Feedback */}
      <StatusAnnouncer />
      <div className={inputFeedback.root}>
        <VolumeIndicator.Root
          className={`${inputFeedback.island.base} ${inputFeedback.island.volume} ${inputFeedback.island.shownVolume}`}
        >
          <VolumeIndicator.Fill className={inputFeedback.island.content}>
            <VolumeHighIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeHigh}`} />
            <VolumeLowIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeLow}`} />
            <VolumeOffIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeOff}`} />
            <VolumeIndicator.Value className={inputFeedback.island.value} />
          </VolumeIndicator.Fill>
        </VolumeIndicator.Root>

        <StatusIndicator.Root
          actions={TOP_STATUS_ACTIONS}
          className={`${inputFeedback.island.base} ${inputFeedback.island.shownStatus}`}
        >
          <div className={inputFeedback.island.content}>
            <CaptionsOnIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownCaptionsOn}`} />
            <CaptionsOffIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownCaptionsOff}`} />
            <FullscreenEnterIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownFullscreenEnter}`} />
            <FullscreenExitIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownFullscreenExit}`} />
            <PipEnterIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownPipEnter}`} />
            <PipExitIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownPipExit}`} />
            <StatusIndicator.Value className={inputFeedback.island.value} />
          </div>
        </StatusIndicator.Root>

        <SeekIndicator.Root className={inputFeedback.bubble.base}>
          <ChevronIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownSeek}`} />
          <SeekIndicator.Value className={inputFeedback.bubble.time} />
        </SeekIndicator.Root>

        <StatusIndicator.Root actions={CENTER_STATUS_ACTIONS} className={inputFeedback.bubble.base}>
          <PlayIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownPlay}`} />
          <PauseIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownPause}`} />
        </StatusIndicator.Root>
      </div>
    </Container>
  );
}

// ================================================================
// Player
// ================================================================

const SEEK_TIME = 10;

// ================================================================
// Components
// ================================================================

const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
  return (
    <button ref={ref} type="button" className={`${button.base} ${button.subtle} ${button.icon} ${className ?? ''}`} {...props} />
  );
});

const SliderRoot = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderRoot({ className, ...props }, ref) {
  return <div ref={ref} className={`${slider.root} ${className ?? ''}`} {...props} />;
});

const SliderTrack = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderTrack(
  { className, ...props },
  ref
) {
  return <div ref={ref} className={`${slider.track} ${className ?? ''}`} {...props} />;
});

const SliderFill = forwardRef<HTMLDivElement, ComponentProps<'div'> & { type?: 'fill' | 'buffer' }>(function SliderFill(
  { type = 'fill', className, ...props },
  ref
) {
  return (
    <div
      ref={ref}
      className={`${slider.fill.base} ${type === 'fill' ? slider.fill.fill : slider.fill.buffer} ${className ?? ''}`}
      {...props}
    />
  );
});

const SliderBuffer = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderBuffer(props, ref) {
  return <SliderFill type="buffer" ref={ref} {...props} />;
});

const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persistent?: boolean }>(function SliderThumb(
  { persistent, className, ...props },
  ref
) {
  return (
    <div
      ref={ref}
      className={`${slider.thumb.base} ${persistent ? slider.thumb.persistent : slider.thumb.interactive} ${className ?? ''}`}
      {...props}
    />
  );
});

function VolumePopover(): ReactNode {
  const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');

  const muteButton = (
    <MuteButton className={iconState.mute.button} render={<Button />}>
      <VolumeOffIcon className={`${icon} ${iconState.mute.volumeOff}`} />
      <VolumeLowIcon className={`${icon} ${iconState.mute.volumeLow}`} />
      <VolumeHighIcon className={`${icon} ${iconState.mute.volumeHigh}`} />
    </MuteButton>
  );

  if (volumeUnsupported) return muteButton;

  return (
    <Popover.Root openOnHover delay={200} closeDelay={100} side="top">
      <Popover.Trigger render={muteButton} />
      <Popover.Popup className={`${popup.popover} ${popup.volume}`}>
        <VolumeSlider.Root orientation="vertical" thumbAlignment="edge" render={<SliderRoot />}>
          <VolumeSlider.Track render={<SliderTrack />}>
            <VolumeSlider.Fill render={<SliderFill />} />
          </VolumeSlider.Track>
          <VolumeSlider.Thumb render={(props) => <SliderThumb persistent {...props} />} />
        </VolumeSlider.Root>
      </Popover.Popup>
    </Popover.Root>
  );
}

// ================================================================
// Utilities
// ================================================================

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isRenderProp(value: unknown): value is RenderProp<unknown> {
  return typeof value === 'function' || isValidElement(value);
}

Default Audio Skin

'use client';

import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { createPlayer, Container, usePlayer, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster, type RenderProp } from '@videojs/react';
import { Audio, audioFeatures } from '@videojs/react/audio';
import './player.css';

function PlaybackRateRadioGroup(): ReactNode {
  const state = usePlaybackRateOptions();
  if (!state) return null;

  const { options, setValue, value } = state;

  return (
    <Menu.RadioGroup className="media-menu__group" value={value} onValueChange={setValue} aria-label="Playback rate">
      {options.map((option) => (
        <Menu.RadioItem key={option.value} className="media-menu__item" value={option.value} disabled={option.disabled}>
          <span>{option.label}</span>
          <Menu.ItemIndicator checked={option.value === value} forceMount className="media-menu__indicator">
            <CheckIcon className="media-icon" />
          </Menu.ItemIndicator>
        </Menu.RadioItem>
      ))}
    </Menu.RadioGroup>
  );
}

function PlaybackRateTrigger(): ReactNode {
  const state = usePlaybackRateOptions();
  if (!state) return null;

  return (
    <Menu.Trigger
      disabled={state.disabled}
      render={<PlaybackRateButton className="media-button--playback-rate" render={<Button />} />}
    />
  );
}

// ================================================================
// Player
// ================================================================

const SEEK_TIME = 10;

export const Player = createPlayer({ features: audioFeatures });

export interface AudioPlayerProps {
  src: string;
  style?: CSSProperties;
  className?: string;
}

/**
 * @example
 * ```tsx
 * <AudioPlayer
 *   src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
 * />
 * ```
 */
export function AudioPlayer({ src, className, ...rest }: AudioPlayerProps): ReactNode {
  return (
    <Player.Provider>
      <Container className={`media-default-skin media-default-skin--audio ${className ?? ''}`} {...rest}>
        <Audio src={src} />

        <ErrorDialog.Root>
          <ErrorDialog.Popup className="media-error">
            <div className="media-error__dialog">
              <div className="media-error__content">
                <ErrorDialog.Title className="media-error__title">Something went wrong.</ErrorDialog.Title>
                <ErrorDialog.Description className="media-error__description" />
              </div>
              <div className="media-error__actions">
                <ErrorDialog.Close className="media-button media-button--subtle">OK</ErrorDialog.Close>
              </div>
            </div>
          </ErrorDialog.Popup>
        </ErrorDialog.Root>

        <div className="media-surface media-controls">
          <Tooltip.Provider>
            <div className="media-button-group">
              <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.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 media-icon--flipped" />
                        <span className="media-icon__label">{SEEK_TIME}</span>
                      </span>
                    </SeekButton>
                  }
                />
                <Tooltip.Popup className="media-surface media-tooltip" />
              </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.Root>
            </div>

            <div className="media-time-controls">
              <Time.Value type="current" className="media-time" />
              <TimeSlider.Root className="media-slider">
                <TimeSlider.Track className="media-slider__track">
                  <TimeSlider.Fill className="media-slider__fill" />
                  <TimeSlider.Buffer className="media-slider__buffer" />
                </TimeSlider.Track>
                <TimeSlider.Thumb className="media-slider__thumb" />
                <TimeSlider.Preview className="media-slider__preview">
                  <TimeSlider.Value type="pointer" className="media-slider__value media-time" />
                </TimeSlider.Preview>
              </TimeSlider.Root>
              <Time.Value type="duration" className="media-time" />
            </div>

            <div className="media-button-group">
              <Menu.Root side="top" align="center" boundary="viewport">
                <PlaybackRateTrigger />
                <Menu.Content className="media-surface media-popover media-menu media-menu--playback-rate">
                  <PlaybackRateRadioGroup />
                </Menu.Content>
              </Menu.Root>

              <VolumePopover />
            </div>
          </Tooltip.Provider>
        </div>

        {/* Hotkeys */}
        <Hotkey keys="Space" action="togglePaused" />
        <Hotkey keys="k" action="togglePaused" />
        <Hotkey keys="m" action="toggleMuted" />
        <Hotkey keys="ArrowRight" action="seekStep" value={5} />
        <Hotkey keys="ArrowLeft" action="seekStep" value={-5} />
        <Hotkey keys="l" action="seekStep" value={10} />
        <Hotkey keys="j" action="seekStep" value={-10} />
        <Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
        <Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
        <Hotkey keys="0-9" action="seekToPercent" />
        <Hotkey keys="Home" action="seekToPercent" value={0} />
        <Hotkey keys="End" action="seekToPercent" value={100} />
        <Hotkey keys=">" action="speedUp" />
        <Hotkey keys="<" action="speedDown" />

        {/* Input Feedback */}
        <StatusAnnouncer />
      </Container>

    </Player.Provider>
  );
}

// ================================================================
// Components
// ================================================================

const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
  return (
    <button
      ref={ref}
      type="button"
      className={`media-button media-button--subtle media-button--icon ${className ?? ''}`}
      {...props}
    />
  );
});

function VolumePopover(): ReactNode {
  const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');

  const muteButton = (
    <MuteButton className="media-button--mute" render={<Button />}>
      <VolumeOffIcon className="media-icon media-icon--volume-off" />
      <VolumeLowIcon className="media-icon media-icon--volume-low" />
      <VolumeHighIcon className="media-icon media-icon--volume-high" />
    </MuteButton>
  );

  if (volumeUnsupported) return muteButton;

  return (
    <Popover.Root openOnHover delay={200} closeDelay={100} side="top" boundary="viewport">
      <Popover.Trigger render={muteButton} />
      <Popover.Popup className="media-surface media-popover media-popover--volume">
        <VolumeSlider.Root className="media-slider" orientation="vertical" thumbAlignment="edge">
          <VolumeSlider.Track className="media-slider__track">
            <VolumeSlider.Fill className="media-slider__fill" />
          </VolumeSlider.Track>
          <VolumeSlider.Thumb className="media-slider__thumb media-slider__thumb--persistent" />
        </VolumeSlider.Root>
      </Popover.Popup>
    </Popover.Root>
  );
}
'use client';

import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons';
import { createPlayer, Container, usePlayer, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster, type RenderProp } from '@videojs/react';
import { Audio, audioFeatures } from '@videojs/react/audio';
import './player.css';

const button = {
  base: "flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-full outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-current focus-visible:outline-offset-2 data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden",
  primary: "bg-white text-black font-medium text-shadow-none",
  subtle: "bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10",
  icon: "grid w-9 aspect-square p-0 active:scale-90",
  live: "inline-flex items-center gap-1.5 aspect-auto w-auto px-3 py-2 text-xs font-semibold uppercase tracking-wider leading-none before:inline-block before:size-2 before:shrink-0 before:rounded-full before:bg-current/40 before:transition-colors before:duration-150 before:ease-out before:content-[\"\"] data-[live-edge]:before:bg-red-500",
};

const buttonGroup = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5";

const controls = "peer/controls @container/media-controls p-[0.375rem] flex items-center gap-x-[0.075rem] rounded-3xl text-shadow-2xs text-shadow-(color:--media-current-shadow-color) bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 text-(--media-text-color) peer-data-open/error:**:invisible";

const error = {
  root: "peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none",
  dialog: "absolute inset-0 z-20 flex items-center gap-3 rounded-full px-5 pr-0.5 bg-(--media-surface-background-color) text-(--media-text-color) backdrop-blur-lg backdrop-saturate-150 transition-[opacity,filter] ease-out duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-xs group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-xs group-data-ending-style/error:delay-0",
  content: "flex flex-1 items-center gap-2",
  title: "font-semibold leading-tight",
  description: "opacity-70 wrap-anywhere",
  actions: "flex gap-2 *:flex-1",
};

const icon = "block [grid-area:1/1] size-(--media-icon-size) shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out";

const iconContainer = "relative grid";

const iconFlipped = "[scale:-1_1]";

const iconState = {
  play: {
    button: "group",
    restart: "hidden opacity-0 group-data-ended:block group-data-ended:opacity-100",
    play: "hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100",
    pause: "hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100",
  },
  mute: {
    button: "group",
    volumeOff: "hidden opacity-0 group-data-muted:block group-data-muted:opacity-100",
    volumeLow: "hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100",
    volumeHigh: "hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100",
  },
  fullscreen: {
    button: "group",
    enter: "hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100",
    exit: "hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100",
  },
  captions: {
    button: "group",
    off: "hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100",
    on: "hidden opacity-0 group-data-active:block group-data-active:opacity-100",
  },
  pip: {
    button: "group",
    off: "hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100",
    on: "hidden opacity-0 group-data-pip:block group-data-pip:opacity-100",
  },
  cast: {
    button: "group",
    enter: "hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100",
    exit: "hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100",
  },
  airplay: {
    button: "group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]",
    enter: "hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100",
    exit: "hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100",
  },
};

const menu = {
  root: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:200ms] [--menu-item-transition-duration:100ms] motion-reduce:[--menu-transition-duration:0ms] motion-reduce:[--menu-item-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-(--media-popover-available-height,none) transition-[transform,scale,opacity,filter,width,height] duration-(--menu-transition-duration) ease-in-out box-border rounded-[1.25rem] p-1.5 overscroll-none min-w-24 overflow-auto",
  settings: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-transition-duration:200ms] [--menu-item-transition-duration:100ms] motion-reduce:[--menu-transition-duration:0ms] motion-reduce:[--menu-item-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-(--media-popover-available-height,none) transition-[transform,scale,opacity,filter,width,height] duration-(--menu-transition-duration) ease-in-out box-border rounded-[1.25rem] p-1.5 overscroll-none [--menu-transition-duration:250ms] relative min-w-44 w-(--media-menu-width) h-(--media-menu-height) overflow-hidden",
  group: "flex flex-col gap-0.5",
  item: "flex cursor-pointer select-none items-center rounded-full py-1.5 px-3 text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-[background-color,color] duration-(--menu-item-transition-duration) ease-out hover:bg-current/10 data-highlighted:bg-current/10 focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between gap-2 tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50",
  indicator: "-mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100 [&_.media-icon]:drop-shadow-[0_1px_0_var(--media-current-shadow-color)]",
  rootView: "absolute inset-0 overflow-auto overscroll-none p-1.5 outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate] data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur",
  submenuPanel: "absolute inset-0 overflow-auto overscroll-none p-1.5 outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full",
  back: "flex cursor-pointer select-none items-center rounded-full py-1.5 px-3 text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-[background-color,color] duration-(--menu-item-transition-duration) ease-out hover:bg-current/10 data-highlighted:bg-current/10 focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full gap-1.5 font-medium text-current/70 hover:text-inherit data-highlighted:text-inherit focus-visible:text-inherit",
  hint: "ml-auto flex min-w-0 items-center gap-1 text-xs text-current/65",
  hintLabel: "max-w-24 overflow-hidden text-ellipsis whitespace-nowrap",
  chevron: "size-3.5 first:-ml-1 last:-mr-1",
  settingsGroup: "group/settings",
  settingsTrigger: "group hidden group-has-[[data-availability=available]]/settings:grid",
  settingsIcon: "transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0",
};

const playbackRate = {
  button: "tabular-nums after:w-[4ch] after:content-[attr(data-rate)_'×'] data-[inline-rate-label]:after:content-none",
};

const popup = {
  popover: "bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset)",
  tooltip: "bg-(--media-surface-background-color) [backdrop-filter:var(--media-surface-backdrop-filter)] [box-shadow:0_0_0_1px_var(--media-surface-outer-border-color),0_1px_3px_0_var(--media-surface-shadow-color),0_1px_2px_-1px_var(--media-surface-shadow-color)] after:absolute after:inset-0 after:[box-shadow:inset_0_0_0_1px_var(--media-surface-inner-border-color)] after:rounded-[inherit] after:pointer-events-none after:z-10 m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)",
  volume: "py-3 px-0 rounded-full",
};

const root = "**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,2rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] [--media-text-color:var(--media-color-primary,oklch(0_0_0))] [--media-surface-background-color:oklch(1_0_0/0.5)] [--media-surface-inner-border-color:oklch(1_0_0/0.1)] [--media-surface-outer-border-color:oklch(0_0_0/0.05)] [--media-surface-shadow-color:oklch(0_0_0/0.15)] [--media-surface-backdrop-filter:blur(16px)_saturate(1.5)] [--media-error-dialog-transition-duration:250ms] [--media-error-dialog-transition-delay:100ms] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-tooltip-side-offset:0.75rem] [--media-tooltip-boundary-offset:0.75rem] [--media-popover-side-offset:0.75rem] [--media-popover-boundary-offset:0.75rem] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-popup-transition-duration:0ms] dark:[--media-surface-background-color:oklch(0_0_0/0.4)] dark:[--media-text-color:var(--media-color-primary,oklch(1_0_0))] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(1_0_0)] contrast-more:[--media-surface-background-color:oklch(1_0_0)] [@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:oklch(0_0_0/0.05)] contrast-more:[--media-surface-outer-border-color:oklch(0_0_0/0.05)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-background-color:oklch(0_0_0)] dark:contrast-more:[--media-surface-background-color:oklch(0_0_0)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-inner-border-color:oklch(1_0_0/0.2)] dark:contrast-more:[--media-surface-inner-border-color:oklch(1_0_0/0.2)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-surface-outer-border-color:transparent] dark:contrast-more:[--media-surface-outer-border-color:transparent]";

const seek = {
  label: "text-[10px] font-medium tracking-tighter tabular-nums",
  labelForward: "absolute -right-px -bottom-0.75",
  labelBackward: "absolute -left-px -bottom-0.75",
};

const slider = {
  root: "group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-20",
  track: "relative isolate overflow-hidden rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-1 data-[orientation=vertical]:w-1 data-[orientation=vertical]:h-full bg-black/10 dark:bg-white/20 dark:ring-1 dark:ring-black/5",
  fill: {
    base: "absolute rounded-[inherit] pointer-events-none",
    fill: "bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill)",
    buffer: "bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)",
  },
  thumb: {
    base: "z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.35),0_1px_2px_-1px_oklch(0_0_0/0.35)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/15 hover:outline-offset-0 focus-visible:outline-current/15 focus-visible:outline-offset-0 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill))]",
    persistent: "size-3",
    interactive: "size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3",
  },
  preview: "group/preview before:block before:min-w-1 before:h-1 before:bg-current before:rounded-full before:opacity-0 before:scale-50 before:shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_2px_0_oklch(0_0_0/0.35)] before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden",
  value: "absolute bottom-9 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none",
};

const time = {
  group: "@container/media-time flex items-center flex-1 gap-3 px-2",
  current: "hidden @2xs/media-time:block tabular-nums",
  duration: "tabular-nums",
};

interface AudioSkinProps {
  src: string;
  style?: CSSProperties;
  className?: string;
}

function PlaybackRateRadioGroup(): ReactNode {
  const state = usePlaybackRateOptions();
  if (!state) return null;

  const { options, setValue, value } = state;

  return (
    <Menu.RadioGroup className={menu.group} value={value} onValueChange={setValue} aria-label="Playback rate">
      {options.map((option) => (
        <Menu.RadioItem key={option.value} className={menu.item} value={option.value} disabled={option.disabled}>
          <span>{option.label}</span>
          <Menu.ItemIndicator checked={option.value === value} forceMount className={menu.indicator}>
            <CheckIcon className={icon} />
          </Menu.ItemIndicator>
        </Menu.RadioItem>
      ))}
    </Menu.RadioGroup>
  );
}

function PlaybackRateTrigger(): ReactNode {
  const state = usePlaybackRateOptions();
  if (!state) return null;

  return (
    <Menu.Trigger
      disabled={state.disabled}
      render={<PlaybackRateButton className={playbackRate.button} render={<Button />} />}
    />
  );
}

export function AudioSkinTailwind({ children, className, ...rest }: AudioSkinProps): ReactNode {

  return (
    <Container className={`${root} ${className ?? ''}`} {...rest}>
      {children}

      <ErrorDialog.Root>
        <ErrorDialog.Popup className={error.root}>
          <div className={error.dialog}>
            <div className={error.content}>
              <ErrorDialog.Title className={error.title}>Something went wrong.</ErrorDialog.Title>
              <ErrorDialog.Description className={error.description} />
            </div>
            <div className={error.actions}>
              <ErrorDialog.Close className={`${button.base} ${button.subtle}`}>OK</ErrorDialog.Close>
            </div>
          </div>
        </ErrorDialog.Popup>
      </ErrorDialog.Root>

      <div className={controls}>
        <Tooltip.Provider>
          <div className={buttonGroup}>
            <Tooltip.Root side="top" boundary="viewport">
              <Tooltip.Trigger
                render={
                  <PlayButton className={iconState.play.button} render={<Button />}>
                    <RestartIcon className={`${icon} ${iconState.play.restart}`} />
                    <PlayIcon className={`${icon} ${iconState.play.play}`} />
                    <PauseIcon className={`${icon} ${iconState.play.pause}`} />
                  </PlayButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>

            <Tooltip.Root side="top" boundary="viewport">
              <Tooltip.Trigger
                render={
                  <SeekButton seconds={-SEEK_TIME} render={<Button />}>
                    <span className={iconContainer}>
                      <SeekIcon className={`${icon} ${iconFlipped}`} />
                      <span className={`${seek.label} ${seek.labelBackward}`}>{SEEK_TIME}</span>
                    </span>
                  </SeekButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>

            <Tooltip.Root side="top" boundary="viewport">
              <Tooltip.Trigger
                render={
                  <SeekButton seconds={SEEK_TIME} render={<Button />}>
                    <span className={iconContainer}>
                      <SeekIcon className={icon} />
                      <span className={`${seek.label} ${seek.labelForward}`}>{SEEK_TIME}</span>
                    </span>
                  </SeekButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>
          </div>

          <div className={time.group}>
            <Time.Value type="current" className={time.current} />
            <TimeSlider.Root render={<SliderRoot />}>
              <TimeSlider.Track render={<SliderTrack />}>
                <TimeSlider.Fill render={<SliderFill />} />
                <TimeSlider.Buffer render={<SliderBuffer />} />
              </TimeSlider.Track>
              <TimeSlider.Thumb render={<SliderThumb />} />
              <TimeSlider.Preview className={slider.preview}>
                <TimeSlider.Value type="pointer" className={slider.value} />
              </TimeSlider.Preview>
            </TimeSlider.Root>
            <Time.Value type="duration" className={time.duration} />
          </div>

          <div className={buttonGroup}>
            <Menu.Root side="top" align="center" boundary="viewport">
              <PlaybackRateTrigger />
              <Menu.Content className={`${popup.popover} ${menu.root}`}>
                <PlaybackRateRadioGroup />
              </Menu.Content>
            </Menu.Root>

            <VolumePopover />
          </div>
        </Tooltip.Provider>
      </div>

      {/* Hotkeys */}
      <Hotkey keys="Space" action="togglePaused" />
      <Hotkey keys="k" action="togglePaused" />
      <Hotkey keys="m" action="toggleMuted" />
      <Hotkey keys="ArrowRight" action="seekStep" value={5} />
      <Hotkey keys="ArrowLeft" action="seekStep" value={-5} />
      <Hotkey keys="l" action="seekStep" value={10} />
      <Hotkey keys="j" action="seekStep" value={-10} />
      <Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
      <Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
      <Hotkey keys="0-9" action="seekToPercent" />
      <Hotkey keys="Home" action="seekToPercent" value={0} />
      <Hotkey keys="End" action="seekToPercent" value={100} />
      <Hotkey keys=">" action="speedUp" />
      <Hotkey keys="<" action="speedDown" />

      {/* Input Feedback */}
      <StatusAnnouncer />
    </Container>
  );
}

// ================================================================
// Player
// ================================================================

const SEEK_TIME = 10;

// ================================================================
// Components
// ================================================================

const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
  return (
    <button ref={ref} type="button" className={`${button.base} ${button.subtle} ${button.icon} ${className ?? ''}`} {...props} />
  );
});

const SliderRoot = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderRoot({ className, ...props }, ref) {
  return <div ref={ref} className={`${slider.root} ${className ?? ''}`} {...props} />;
});

const SliderTrack = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderTrack(
  { className, ...props },
  ref
) {
  return <div ref={ref} className={`${slider.track} ${className ?? ''}`} {...props} />;
});

const SliderFill = forwardRef<HTMLDivElement, ComponentProps<'div'> & { type?: 'fill' | 'buffer' }>(function SliderFill(
  { type = 'fill', className, ...props },
  ref
) {
  return (
    <div
      ref={ref}
      className={`${slider.fill.base} ${type === 'fill' ? slider.fill.fill : slider.fill.buffer} ${className ?? ''}`}
      {...props}
    />
  );
});

const SliderBuffer = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderBuffer(props, ref) {
  return <SliderFill type="buffer" ref={ref} {...props} />;
});

const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persistent?: boolean }>(function SliderThumb(
  { persistent, className, ...props },
  ref
) {
  return (
    <div
      ref={ref}
      className={`${slider.thumb.base} ${persistent ? slider.thumb.persistent : slider.thumb.interactive} ${className ?? ''}`}
      {...props}
    />
  );
});

function VolumePopover(): ReactNode {
  const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');

  const muteButton = (
    <MuteButton className={iconState.mute.button} render={<Button />}>
      <VolumeOffIcon className={`${icon} ${iconState.mute.volumeOff}`} />
      <VolumeLowIcon className={`${icon} ${iconState.mute.volumeLow}`} />
      <VolumeHighIcon className={`${icon} ${iconState.mute.volumeHigh}`} />
    </MuteButton>
  );

  if (volumeUnsupported) return muteButton;

  return (
    <Popover.Root openOnHover delay={200} closeDelay={100} side="top" boundary="viewport">
      <Popover.Trigger render={muteButton} />
      <Popover.Popup className={`${popup.popover} ${popup.volume}`}>
        <VolumeSlider.Root orientation="vertical" thumbAlignment="edge" render={<SliderRoot />}>
          <VolumeSlider.Track render={<SliderTrack />}>
            <VolumeSlider.Fill render={<SliderFill />} />
          </VolumeSlider.Track>
          <VolumeSlider.Thumb render={(props) => <SliderThumb persistent {...props} />} />
        </VolumeSlider.Root>
      </Popover.Popup>
    </Popover.Root>
  );
}

Minimal Video Skin

'use client';

import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode, isValidElement } from 'react';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { createPlayer, Poster, Container, usePlayer, AirPlayButton, BufferingIndicator, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, Popover, SeekButton, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider, type RenderProp } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './player.css';

const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;

const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;

function MenuChevron({ flipped = false }: { flipped?: boolean }): ReactNode {
  return <ChevronIcon className={`media-icon media-menu__chevron ${flipped ? 'media-icon--flipped' : undefined}`} />;
}

function SettingsMenu(): ReactNode {
  const playbackRate = usePlaybackRateOptions();
  const captions = useCaptionsOptions();
  const hasPlaybackRate = playbackRate?.state.availability === 'available';
  const hasCaptions = captions?.state.availability === 'available';

  if (!hasPlaybackRate && !hasCaptions) return null;

  return (
    <Menu.Root side="top" align="center">
      <Menu.Trigger aria-label="Settings" className="media-button--settings" render={<Button />}>
        <GearIcon className="media-icon media-icon--settings" />
      </Menu.Trigger>
      <Menu.Content className="media-popover media-menu media-menu--settings">
        <Menu.View className="media-menu__panel">
          <div className="media-menu__group">
            {hasPlaybackRate && playbackRate ? (
              <Menu.Root>
                <Menu.Trigger
                  type="playback-rate"
                  className="media-menu__item media-menu__item--submenu"
                  render={(props) => (
                    <div {...props}>
                      <span>Speed</span>
                      <span className="media-menu__hint">
                        <Menu.ItemValue className="media-menu__hint-label" />
                        <MenuChevron />
                      </span>
                    </div>
                  )}
                />
                <Menu.Content className="media-menu__panel">
                  <Menu.Back className="media-menu__back">
                    <MenuChevron flipped />
                    Speed
                  </Menu.Back>
                  <Menu.RadioGroup
                    className="media-menu__group"
                    value={playbackRate.value}
                    onValueChange={playbackRate.setValue}
                    aria-label="Playback rate"
                  >
                    {playbackRate.options.map((option) => (
                      <Menu.RadioItem
                        key={option.value}
                        className="media-menu__item"
                        value={option.value}
                        disabled={option.disabled}
                      >
                        <span>{option.label}</span>
                        <Menu.ItemIndicator
                          checked={option.value === playbackRate.value}
                          forceMount
                          className="media-menu__indicator"
                        >
                          <CheckIcon className="media-icon" />
                        </Menu.ItemIndicator>
                      </Menu.RadioItem>
                    ))}
                  </Menu.RadioGroup>
                </Menu.Content>
              </Menu.Root>
            ) : null}
            {hasCaptions && captions ? (
              <Menu.Root>
                <Menu.Trigger
                  type="captions"
                  className="media-menu__item media-menu__item--submenu"
                  render={(props) => (
                    <div {...props}>
                      <span>Captions</span>
                      <span className="media-menu__hint">
                        <Menu.ItemValue className="media-menu__hint-label" />
                        <MenuChevron />
                      </span>
                    </div>
                  )}
                />
                <Menu.Content className="media-menu__panel">
                  <Menu.Back className="media-menu__back">
                    <MenuChevron flipped />
                    Captions
                  </Menu.Back>
                  <Menu.RadioGroup
                    className="media-menu__group"
                    value={captions.value}
                    onValueChange={captions.setValue}
                    aria-label="Captions"
                  >
                    {captions.options.map((option) => (
                      <Menu.RadioItem
                        key={option.value}
                        className="media-menu__item"
                        value={option.value}
                        disabled={option.disabled}
                      >
                        <span>{option.label}</span>
                        <Menu.ItemIndicator
                          checked={option.value === captions.value}
                          forceMount
                          className="media-menu__indicator"
                        >
                          <CheckIcon className="media-icon" />
                        </Menu.ItemIndicator>
                      </Menu.RadioItem>
                    ))}
                  </Menu.RadioGroup>
                </Menu.Content>
              </Menu.Root>
            ) : null}
          </div>
        </Menu.View>
      </Menu.Content>
    </Menu.Root>
  );
}

// ================================================================
// Player
// ================================================================

const SEEK_TIME = 10;

export const Player = createPlayer({ features: videoFeatures });

export interface VideoPlayerProps {
  src: string;
  style?: CSSProperties;
  className?: string;
  poster?: string | RenderProp<Poster.State> | undefined;
}

/**
 * @example
 * ```tsx
 * <VideoPlayer
 *   src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
 *   poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp"
 * />
 * ```
 */
export function VideoPlayer({ src, className, poster, ...rest }: VideoPlayerProps): ReactNode {
  return (
    <Player.Provider>
      <Container className={`media-minimal-skin media-minimal-skin--video ${className ?? ''}`} {...rest}>
        <Video src={src} playsInline />

        {poster && (
          <Poster src={isString(poster) ? poster : undefined} render={isRenderProp(poster) ? poster : undefined} />
        )}

        <BufferingIndicator
          render={(props) => (
            <div {...props} className="media-buffering-indicator">
              <SpinnerIcon className="media-icon" />
            </div>
          )}
        />

        <ErrorDialog.Root>
          <ErrorDialog.Popup className="media-error">
            <div className="media-error__dialog">
              <div className="media-error__content">
                <ErrorDialog.Title className="media-error__title">Something went wrong.</ErrorDialog.Title>
                <ErrorDialog.Description className="media-error__description" />
              </div>
              <div className="media-error__actions">
                <ErrorDialog.Close className="media-button media-button--primary">OK</ErrorDialog.Close>
              </div>
            </div>
          </ErrorDialog.Popup>
        </ErrorDialog.Root>

        <Controls.Root className="media-controls">
          <Tooltip.Provider>
            <div className="media-button-group">
              <Tooltip.Root side="top">
                <Tooltip.Trigger
                  render={
                    <PlayButton className="media-button--play" render={<Button />}>
                      <RestartIcon className="media-icon media-icon--restart" />
                      <PlayIcon className="media-icon media-icon--play" />
                      <PauseIcon className="media-icon media-icon--pause" />
                    </PlayButton>
                  }
                />
                <Tooltip.Popup className="media-tooltip" />
              </Tooltip.Root>

              <Tooltip.Root side="top">
                <Tooltip.Trigger
                  render={
                    <SeekButton seconds={-SEEK_TIME} className="media-button--seek" render={<Button />}>
                      <span className="media-icon__container">
                        <SeekIcon className="media-icon media-icon--seek media-icon--flipped" />
                        <span className="media-icon__label">{SEEK_TIME}</span>
                      </span>
                    </SeekButton>
                  }
                />
                <Tooltip.Popup className="media-tooltip" />
              </Tooltip.Root>

              <Tooltip.Root side="top">
                <Tooltip.Trigger
                  render={
                    <SeekButton seconds={SEEK_TIME} className="media-button--seek" render={<Button />}>
                      <span className="media-icon__container">
                        <SeekIcon className="media-icon media-icon--seek" />
                        <span className="media-icon__label">{SEEK_TIME}</span>
                      </span>
                    </SeekButton>
                  }
                />
                <Tooltip.Popup className="media-tooltip" />
              </Tooltip.Root>
            </div>

            <div className="media-time-controls">
              <Time.Group className="media-time-group">
                <Time.Value type="current" className="media-time media-time--current" />
                <Time.Separator className="media-time-separator" />
                <Time.Value type="duration" className="media-time media-time--duration" />
              </Time.Group>

              <TimeSlider.Root className="media-slider">
                <TimeSlider.Track className="media-slider__track">
                  <TimeSlider.Fill className="media-slider__fill" />
                  <TimeSlider.Buffer className="media-slider__buffer" />
                </TimeSlider.Track>
                <TimeSlider.Thumb className="media-slider__thumb" />

                <div className="media-thumbnail media-slider__thumbnail">
                  <div className="media-thumbnail__image-wrapper">
                    <Slider.Thumbnail className="media-thumbnail__image" />
                  </div>
                  <TimeSlider.Value type="pointer" className="media-time media-thumbnail__time" />
                  <SpinnerIcon className="media-thumbnail__spinner media-icon" />
                </div>
                <TimeSlider.Preview className="media-slider__preview">
                  <TimeSlider.Value type="pointer" className="media-time media-slider__value" />
                </TimeSlider.Preview>
              </TimeSlider.Root>
            </div>

            <div className="media-button-group">
              <VolumePopover />

              <SettingsMenu />

              <Tooltip.Root side="top">
                <Tooltip.Trigger
                  render={
                    <CastButton className="media-button--cast" render={<Button />}>
                      <CastEnterIcon className="media-icon media-icon--cast-enter" />
                      <CastExitIcon className="media-icon media-icon--cast-exit" />
                    </CastButton>
                  }
                />
                <Tooltip.Popup className="media-tooltip" />
              </Tooltip.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.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.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.Root>
            </div>
          </Tooltip.Provider>
        </Controls.Root>

        <div className="media-overlay" />

        {/* Hotkeys */}
        <Hotkey keys="Space" action="togglePaused" />
        <Hotkey keys="k" action="togglePaused" />
        <Hotkey keys="m" action="toggleMuted" />
        <Hotkey keys="f" action="toggleFullscreen" />
        <Hotkey keys="c" action="toggleSubtitles" />
        <Hotkey keys="i" action="togglePictureInPicture" />
        <Hotkey keys="ArrowRight" action="seekStep" value={SEEK_TIME / 2} />
        <Hotkey keys="ArrowLeft" action="seekStep" value={-(SEEK_TIME / 2)} />
        <Hotkey keys="l" action="seekStep" value={SEEK_TIME} />
        <Hotkey keys="j" action="seekStep" value={-SEEK_TIME} />
        <Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
        <Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
        <Hotkey keys="0-9" action="seekToPercent" />
        <Hotkey keys="Home" action="seekToPercent" value={0} />
        <Hotkey keys="End" action="seekToPercent" value={100} />
        <Hotkey keys=">" action="speedUp" />
        <Hotkey keys="<" action="speedDown" />

        {/* Gestures */}
        <Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
        <Gesture type="tap" action="toggleControls" pointer="touch" />
        <Gesture type="doubletap" action="seekStep" value={-SEEK_TIME} region="left" />
        <Gesture type="doubletap" action="toggleFullscreen" region="center" />
        <Gesture type="doubletap" action="seekStep" value={SEEK_TIME} region="right" />

        {/* Input Feedback */}
        <StatusAnnouncer />
        <div className="media-input-feedback">
          <VolumeIndicator.Root className="media-input-feedback-island media-input-feedback-island--volume">
            <VolumeIndicator.Fill className="media-input-feedback-island__content">
              <VolumeHighIcon className="media-icon media-icon--volume-high" />
              <VolumeLowIcon className="media-icon media-icon--volume-low" />
              <VolumeOffIcon className="media-icon media-icon--volume-off" />
              <div className="media-input-feedback-island__progress" aria-hidden="true" />
              <VolumeIndicator.Value className="media-input-feedback-island__value" />
            </VolumeIndicator.Fill>
          </VolumeIndicator.Root>

          <StatusIndicator.Root
            actions={TOP_STATUS_ACTIONS}
            className="media-input-feedback-island media-input-feedback-island--status"
          >
            <div className="media-input-feedback-island__content">
              <CaptionsOnIcon className="media-icon media-icon--captions-on" />
              <CaptionsOffIcon className="media-icon media-icon--captions-off" />
              <FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" />
              <FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" />
              <PipEnterIcon className="media-icon media-icon--pip-enter" />
              <PipExitIcon className="media-icon media-icon--pip-exit" />
              <StatusIndicator.Value className="media-input-feedback-island__value" />
            </div>
          </StatusIndicator.Root>

          <SeekIndicator.Root className="media-input-feedback-bubble">
            <ChevronIcon className="media-icon media-icon--seek" />
            <SeekIndicator.Value className="media-time" />
          </SeekIndicator.Root>

          <StatusIndicator.Root actions={CENTER_STATUS_ACTIONS} className="media-input-feedback-bubble">
            <PlayIcon className="media-icon media-icon--play" />
            <PauseIcon className="media-icon media-icon--pause" />
          </StatusIndicator.Root>
        </div>
      </Container>

    </Player.Provider>
  );
}

// ================================================================
// Components
// ================================================================

const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
  return (
    <button
      ref={ref}
      type="button"
      className={`media-button media-button--subtle media-button--icon ${className ?? ''}`}
      {...props}
    />
  );
});

function VolumePopover(): ReactNode {
  const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');

  const muteButton = (
    <MuteButton className="media-button--mute" render={<Button />}>
      <VolumeOffIcon className="media-icon media-icon--volume-off" />
      <VolumeLowIcon className="media-icon media-icon--volume-low" />
      <VolumeHighIcon className="media-icon media-icon--volume-high" />
    </MuteButton>
  );

  if (volumeUnsupported) return muteButton;

  return (
    <Popover.Root openOnHover delay={200} closeDelay={100} side="top">
      <Popover.Trigger render={muteButton} />
      <Popover.Popup className="media-popover media-popover--volume">
        <VolumeSlider.Root className="media-slider" orientation="vertical" thumbAlignment="edge">
          <VolumeSlider.Track className="media-slider__track">
            <VolumeSlider.Fill className="media-slider__fill" />
          </VolumeSlider.Track>
          <VolumeSlider.Thumb className="media-slider__thumb media-slider__thumb--persistent" />
        </VolumeSlider.Root>
      </Popover.Popup>
    </Popover.Root>
  );
}

// ================================================================
// Utilities
// ================================================================

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isRenderProp(value: unknown): value is RenderProp<unknown> {
  return typeof value === 'function' || isValidElement(value);
}
'use client';

import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode, isValidElement } from 'react';
import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, RestartIcon, SeekIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { createPlayer, Poster, Container, usePlayer, AirPlayButton, BufferingIndicator, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, Popover, SeekButton, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider, type RenderProp } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './player.css';

const bufferingIndicator = "absolute inset-0 hidden items-center justify-center pointer-events-none text-white not-data-visible:[--media-spinner-animation:none] data-visible:flex";

const button = {
  base: "flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden",
  primary: "bg-white text-black font-medium text-shadow-none",
  subtle: "bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10",
  icon: "grid w-[2.375rem] aspect-square p-0 active:scale-90",
  live: "inline-flex items-center gap-1.5 aspect-auto w-auto px-3 py-2 text-xs font-semibold uppercase tracking-wider leading-none before:inline-block before:size-2 before:shrink-0 before:rounded-full before:bg-current/40 before:transition-colors before:duration-150 before:ease-out before:content-[\"\"] data-[live-edge]:before:bg-red-500",
};

const buttonGroupEnd = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 justify-end @2xl/media-root:flex-none";

const buttonGroupStart = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5 flex-1 @2xl/media-root:flex-none";

const controls = "peer/controls @container/media-controls flex items-center bg-(--media-controls-background-color) [backdrop-filter:var(--media-controls-backdrop-filter)] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) absolute bottom-1 inset-x-1 p-1 gap-x-2 flex-wrap rounded-xl text-white z-10 peer-data-open/error:hidden ease-(--media-controls-transition-timing-function) duration-(--media-controls-transition-duration) pointer-fine:will-change-[translate,filter,opacity] pointer-fine:transition-[translate,filter,opacity] pointer-coarse:will-change-[translate,opacity] pointer-coarse:transition-[translate,opacity] not-data-visible:opacity-0 not-data-visible:pointer-events-none motion-safe:not-data-visible:translate-y-full pointer-fine:motion-safe:not-data-visible:blur-sm @2xl/media-root:flex-nowrap @2xl/media-root:bottom-2 @2xl/media-root:inset-x-2 @2xl/media-root:*:[--media-popover-side-offset:0rem]";

const error = {
  root: "peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none pointer-events-none outline-none",
  dialog: "flex flex-col gap-3 max-w-64 p-4 text-white text-shadow-2xs text-shadow-black/50 transition-[opacity,scale,transform] duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) ease-(--media-error-dialog-transition-timing-function) group-data-starting-style/error:opacity-0 group-data-starting-style/error:scale-50 group-data-ending-style/error:opacity-0 group-data-ending-style/error:scale-50 group-data-ending-style/error:delay-0 pointer-events-auto",
  content: "flex flex-col gap-2 py-1.5",
  title: "font-semibold leading-tight text-lg",
  description: "opacity-70 wrap-anywhere",
  actions: "flex gap-2 *:flex-1",
};

const icon = "block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out";

const iconContainer = "relative grid";

const iconFlipped = "[scale:-1_1]";

const iconState = {
  play: {
    button: "group",
    restart: "hidden opacity-0 group-data-ended:block group-data-ended:opacity-100",
    play: "hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100",
    pause: "hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100",
  },
  mute: {
    button: "group",
    volumeOff: "hidden opacity-0 group-data-muted:block group-data-muted:opacity-100",
    volumeLow: "hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100",
    volumeHigh: "hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100",
  },
  fullscreen: {
    button: "group",
    enter: "hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100",
    exit: "hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100",
  },
  captions: {
    button: "group",
    off: "hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100",
    on: "hidden opacity-0 group-data-active:block group-data-active:opacity-100",
  },
  pip: {
    button: "group",
    off: "hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100",
    on: "hidden opacity-0 group-data-pip:block group-data-pip:opacity-100",
  },
  cast: {
    button: "group",
    enter: "hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100",
    exit: "hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100",
  },
  airplay: {
    button: "group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]",
    enter: "hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100",
    exit: "hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100",
  },
};

const inputFeedback = {
  root: "absolute inset-x-0 top-0 bottom-14 pointer-events-none grid grid-cols-3 items-center justify-items-center overflow-hidden rounded-[inherit] @2xl/media-root:bottom-0 [color:var(--media-color-primary,oklch(1_0_0))]",
  island: {
    base: "group/input-indicator absolute top-0 inset-x-0 pt-3 pb-32 flex justify-center text-inherit font-medium origin-top pointer-events-none duration-100 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-400 data-starting-style:ease-in data-ending-style:duration-400 data-ending-style:ease-in [background-image:linear-gradient(to_bottom,oklch(0_0_0/0.35),oklch(0_0_0/0.2)_3rem,oklch(0_0_0/0))] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) pointer-fine:will-change-[translate,filter,opacity] pointer-fine:transition-[translate,filter,opacity] pointer-coarse:will-change-[translate,opacity] pointer-coarse:transition-[translate,opacity] pointer-fine:motion-safe:data-starting-style:blur-sm pointer-fine:motion-safe:data-ending-style:blur-sm motion-safe:data-ending-style:-translate-y-full",
    content: "flex justify-between items-center gap-2 px-2.5 py-1 *:last:ml-auto [@media(prefers-reduced-transparency:reduce)]:bg-(--media-controls-background-color) [@media(prefers-reduced-transparency:reduce)]:rounded-lg contrast-more:bg-(--media-controls-background-color) contrast-more:rounded-lg",
    volume: "*:data-feedback-island-content:w-[min(80%,14rem)]",
    volumeProgress: "[--media-progress-fill:var(--media-volume-fill)] w-full h-0.75 rounded-full [background-image:linear-gradient(to_right,currentColor_0%,currentColor_var(--media-progress-fill),oklch(from_currentColor_l_c_h/0.2)_var(--media-progress-fill),oklch(from_currentColor_l_c_h/0.2)_100%)] shadow-[0_1px_0_var(--media-current-shadow-color-subtle)]",
    shownVolume: "data-open:duration-100 data-min:*:data-feedback-island-content:animate-media-shake data-max:*:data-feedback-island-content:animate-media-shake motion-reduce:data-min:*:data-feedback-island-content:animate-none motion-reduce:data-max:*:data-feedback-island-content:animate-none",
    shownStatus: "data-open:duration-100",
    icon: "hidden shrink-0 drop-shadow-[0_1px_0_var(--media-current-shadow-color)]",
    shownVolumeHigh: "group-data-[level=high]/input-indicator:block",
    shownVolumeLow: "group-data-[level=low]/input-indicator:block",
    shownVolumeOff: "group-data-[level=off]/input-indicator:block",
    shownCaptionsOn: "group-data-[status=captions-on]/input-indicator:block",
    shownCaptionsOff: "group-data-[status=captions-off]/input-indicator:block",
    shownFullscreenEnter: "group-data-[status=fullscreen]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=fullscreen]/input-indicator:animate-media-pop-in",
    shownFullscreenExit: "group-data-[status=exit-fullscreen]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=exit-fullscreen]/input-indicator:animate-media-pop-in",
    shownPipEnter: "group-data-[status=pip]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=pip]/input-indicator:animate-media-pop-in",
    shownPipExit: "group-data-[status=exit-pip]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=exit-pip]/input-indicator:animate-media-pop-in",
    value: "ml-auto",
  },
  bubble: {
    base: "group/input-indicator col-start-2 row-start-1 flex flex-col items-center justify-center p-4 transition-opacity duration-250 ease-out data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:duration-200 data-starting-style:ease-in data-ending-style:duration-200 data-ending-style:ease-in @2xl/media-root:p-8 not-data-direction:[transition-property:opacity,scale] not-data-direction:duration-600 not-data-direction:[transition-timing-function:ease-out,linear(0,0.12_1.5%,1.35_9.7%,2.2_13.9%,3_19.9%,2.7_21.8%,0.62_37.5%,0.96_50.9%,1)] motion-reduce:not-data-direction:transition-opacity motion-reduce:not-data-direction:duration-100 motion-reduce:not-data-direction:ease-out not-data-direction:data-starting-style:scale-80 not-data-direction:data-ending-style:scale-80 not-data-direction:data-starting-style:duration-200 not-data-direction:data-starting-style:ease-in not-data-direction:data-ending-style:duration-200 not-data-direction:data-ending-style:ease-in data-[direction=backward]:col-start-1 data-[direction=backward]:justify-self-start data-[direction=forward]:col-start-3 data-[direction=forward]:justify-self-end",
    icon: "hidden w-9 h-9",
    shownSeek: "group-data-direction/input-indicator:block group-data-[direction=backward]/input-indicator:-scale-x-100 group-not-data-starting-style/input-indicator:group-data-[direction=forward]/input-indicator:animate-media-slide-in-forward group-not-data-starting-style/input-indicator:group-data-[direction=backward]/input-indicator:animate-media-slide-in-backward motion-reduce:group-data-direction/input-indicator:animate-none",
    shownPause: "group-data-[status=pause]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=pause]/input-indicator:animate-media-pop-in",
    shownPlay: "group-data-[status=play]/input-indicator:block motion-safe:group-not-data-starting-style/input-indicator:group-data-[status=play]/input-indicator:animate-media-pop-in",
    time: "tabular-nums",
  },
};

const menu = {
  root: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-item-height:1.875rem] [--menu-transition-duration:200ms] [--menu-item-transition-duration:100ms] motion-reduce:[--menu-transition-duration:0ms] motion-reduce:[--menu-item-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-(--media-popover-available-height,none) bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] transition-[transform,scale,opacity,filter,width,height] duration-(--menu-transition-duration) ease-in-out box-border rounded-xl p-1 overscroll-none min-w-24 overflow-auto",
  settings: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-item-height:1.875rem] [--menu-transition-duration:200ms] [--menu-item-transition-duration:100ms] motion-reduce:[--menu-transition-duration:0ms] motion-reduce:[--menu-item-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-(--media-popover-available-height,none) bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] transition-[transform,scale,opacity,filter,width,height] duration-(--menu-transition-duration) ease-in-out box-border rounded-xl p-1 overscroll-none [--menu-transition-duration:250ms] relative min-w-44 w-(--media-menu-width) h-(--media-menu-height) overflow-hidden",
  group: "flex flex-col gap-0.5",
  item: "flex h-(--menu-item-height) cursor-pointer select-none items-center rounded-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-[background-color,color] duration-(--menu-item-transition-duration) ease-out hover:bg-current/10 data-highlighted:bg-current/10 focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between gap-2 px-2.5 tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50",
  indicator: "-mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100",
  rootView: "absolute inset-0 overflow-auto overscroll-none p-1 outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate] data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur",
  submenuPanel: "absolute inset-0 overflow-auto overscroll-none p-1 outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full",
  back: "flex h-(--menu-item-height) cursor-pointer select-none items-center rounded-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-[background-color,color] duration-(--menu-item-transition-duration) ease-out hover:bg-current/10 data-highlighted:bg-current/10 focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full gap-1.5 px-2.5 font-medium text-current/70 hover:text-inherit data-highlighted:text-inherit focus-visible:text-inherit",
  hint: "ml-auto flex min-w-0 items-center gap-1 text-xs text-current/65",
  hintLabel: "max-w-24 overflow-hidden text-ellipsis whitespace-nowrap",
  chevron: "size-3.5 first:-ml-1 last:-mr-1",
  settingsGroup: "group/settings",
  settingsTrigger: "group hidden group-has-[[data-availability=available]]/settings:grid",
  settingsIcon: "transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0",
};

const overlay = "absolute inset-0 flex flex-col items-start pointer-events-none rounded-[inherit] opacity-0 bg-linear-to-t from-black/70 via-black/50 via-[7.5rem] to-transparent backdrop-blur-none backdrop-saturate-100 transition-[opacity,backdrop-filter] duration-(--media-controls-transition-duration) ease-out peer-data-visible/controls:opacity-100 peer-data-open/error:opacity-100 peer-data-open/error:duration-(--media-error-dialog-transition-duration) peer-data-open/error:delay-(--media-error-dialog-transition-delay) peer-data-open/error:backdrop-blur-lg peer-data-open/error:backdrop-saturate-120";

const popup = {
  base: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full",
  popover: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset)",
  tooltip: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/10 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)",
  volume: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) py-3 px-0 bg-transparent rounded-xl [@media(prefers-reduced-transparency:reduce)]:bg-(--media-controls-background-color) contrast-more:bg-(--media-controls-background-color)",
};

const poster = () => "absolute inset-0 w-full h-full pointer-events-none transition-opacity duration-250 not-data-visible:opacity-0 rounded-[inherit] [object-fit:var(--media-object-fit,contain)] [object-position:var(--media-object-position,center)]";

const root = () => "**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,0.75rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] bg-black overflow-clip after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10 after:inset-0 after:ring-1 after:ring-inset after:ring-black/15 dark:after:ring-white/15 [&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&_video]:[object-fit:var(--media-object-fit,contain)] [&_video]:[object-position:var(--media-object-position,center)] [--media-video-border-radius:var(--media-border-radius,0.75rem)] [--media-controls-background-color:transparent] [--media-controls-transition-duration:100ms] [--media-controls-transition-timing-function:ease-out] [--media-error-dialog-transition-duration:150ms] [--media-error-dialog-transition-delay:100ms] [--media-error-dialog-transition-timing-function:ease-out] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-tooltip-background-color:oklch(1_0_0/0.1)] [--media-tooltip-border-color:transparent] [--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)] [--media-tooltip-text-color:currentColor] [--media-tooltip-side-offset:0.5rem] [--media-tooltip-boundary-offset:0.5rem] [--media-popover-background-color:oklch(1_0_0/0.1)] [--media-popover-border-color:transparent] [--media-popover-backdrop-filter:blur(16px)_saturate(1.5)] [--media-popover-side-offset:1.5rem] [--media-popover-boundary-offset:0.5rem] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-popup-transition-duration:0ms] [@media(prefers-reduced-transparency:reduce)]:[--media-controls-background-color:oklch(0_0_0)] contrast-more:[--media-controls-background-color:oklch(0_0_0)] [@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(0_0_0)] contrast-more:[--media-tooltip-background-color:oklch(0_0_0)] @2xl/media-root:*:[--media-popover-side-offset:0.5rem] pointer-fine:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:300ms] pointer-coarse:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:150ms] motion-reduce:has-[[data-controls]:not([data-visible])]:[--media-controls-transition-duration:50ms] [--media-caption-track-y:-0.5rem] [--media-caption-track-delay:25ms] [--media-caption-track-duration:var(--media-controls-transition-duration)] has-[[data-controls][data-visible]]:[--media-caption-track-y:-5rem] @2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:-3rem] [&_video::-webkit-media-text-track-container]:transition-[translate] [&_video::-webkit-media-text-track-container]:duration-(--media-caption-track-duration) [&_video::-webkit-media-text-track-container]:ease-out [&_video::-webkit-media-text-track-container]:delay-(--media-caption-track-delay) [&_video::-webkit-media-text-track-container]:translate-y-(--media-caption-track-y) [&_video::-webkit-media-text-track-container]:scale-98 [&_video::-webkit-media-text-track-container]:z-1 [&_video::-webkit-media-text-track-container]:font-[inherit] [&:fullscreen]:[--media-border-radius:0] [&:fullscreen_video]:object-contain";

const seek = {
  label: "text-[10px] font-medium tracking-tighter tabular-nums",
  labelForward: "absolute -right-px -bottom-0.75",
  labelBackward: "absolute -left-px -bottom-0.75",
};

const slider = {
  root: "group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]",
  track: "relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-0.75 data-[orientation=vertical]:w-0.75 data-[orientation=vertical]:h-full ring-1 ring-black/5",
  fill: {
    base: "absolute rounded-[inherit] pointer-events-none",
    fill: "bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill,0) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill,0)",
    buffer: "bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer,0) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)",
  },
  thumb: {
    base: "z-10 absolute size-3 -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,scale,outline-offset] duration-150 ease-out select-none outline-2 outline-transparent -outline-offset-2 focus-visible:outline-current focus-visible:outline-offset-2 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill,0) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill,0))]",
    interactive: "opacity-0 scale-70 origin-center group-hover/slider:opacity-100 group-hover/slider:scale-100 group-focus-within/slider:opacity-100 group-focus-within/slider:scale-100",
  },
  preview: "group/preview before:block before:bg-current/35 before:opacity-0 before:scale-50 before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 data-[orientation=horizontal]:before:min-w-px data-[orientation=horizontal]:before:h-5 data-[orientation=vertical]:before:w-5 data-[orientation=vertical]:before:min-h-px peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden",
  value: "absolute bottom-6 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none",
};

const thumbnail = {
  root: "group/thumbnail peer/thumbnail pointer-events-none [--media-slider-thumbnail-max-width:11rem] [--media-slider-thumbnail-padding:-0.5rem] [--media-slider-thumbnail-inset:calc(100cqi-100%)] absolute [left:clamp(calc(var(--media-slider-thumbnail-max-width)/2+var(--media-slider-thumbnail-padding)),var(--media-slider-pointer),calc(100%-var(--media-slider-thumbnail-max-width)/2-var(--media-slider-thumbnail-padding)+var(--media-slider-thumbnail-inset)))] bottom-full -translate-x-1/2 @2xl/media-root:[left:var(--media-slider-pointer)] opacity-0 scale-80 blur-sm origin-bottom transition-[scale,opacity,filter] duration-150 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:opacity-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:scale-100 has-[[role=img]:not([data-hidden])]:group-data-pointing/slider:blur-none has-[[role=img][data-loading]]:max-h-24",
  imageWrapper: "relative rounded-lg bg-black/90 after:absolute after:inset-0 after:rounded-[inherit] after:ring-1 after:ring-black/5 after:shadow-sm after:shadow-black/20",
  image: "block rounded-[inherit] transition-opacity duration-150 ease-out data-loading:opacity-0 max-w-(--media-slider-thumbnail-max-width)",
  time: "mt-2 block text-center tabular-nums",
  spinner: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity duration-150 ease-out group-not-has-[[role=img][data-loading]]/thumbnail:[--media-spinner-animation:none] group-has-[[role=img][data-loading]]/thumbnail:opacity-100",
};

const time = {
  group: "flex items-center gap-1",
  current: "hidden tabular-nums @2xl/media-root:inline",
  separator: "hidden @2xl/media-root:inline @2xl/media-root:text-current/60",
  duration: "tabular-nums @2xl/media-root:text-current/60",
  controls: "@container flex flex-row-reverse items-center flex-1 gap-3 @2xl/media-root:flex-row grow-0 shrink-0 basis-full order-[-1] px-2.5 @2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]",
};

interface MinimalVideoSkinProps {
  src: string;
  style?: CSSProperties;
  className?: string;
  poster?: string | RenderProp<Poster.State> | undefined;
}

const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const;

const CENTER_STATUS_ACTIONS = ['togglePaused'] as const;

function MenuChevron({ flipped = false }: { flipped?: boolean }): ReactNode {
  return <ChevronIcon className={`${icon} ${menu.chevron} ${flipped ? iconFlipped : undefined}`} />;
}

function SettingsMenu(): ReactNode {
  const playbackRate = usePlaybackRateOptions();
  const captions = useCaptionsOptions();
  const hasPlaybackRate = playbackRate?.state.availability === 'available';
  const hasCaptions = captions?.state.availability === 'available';

  if (!hasPlaybackRate && !hasCaptions) return null;

  return (
    <Menu.Root side="top" align="center">
      <Menu.Trigger
        aria-label="Settings"
        className="media-button--settings"
        render={<Button className={`${button.icon} ${menu.settingsTrigger}`} />}
      >
        <GearIcon className={`${icon} ${menu.settingsIcon}`} />
      </Menu.Trigger>
      <Menu.Content className={menu.settings}>
        <Menu.View className={menu.rootView}>
          <div className={menu.group}>
            {hasPlaybackRate && playbackRate ? (
              <Menu.Root>
                <Menu.Trigger
                  type="playback-rate"
                  className={`${menu.item} media-menu__item--submenu`}
                  render={(props) => (
                    <div {...props}>
                      <span>Speed</span>
                      <span className={menu.hint}>
                        <Menu.ItemValue className={menu.hintLabel} />
                        <MenuChevron />
                      </span>
                    </div>
                  )}
                />
                <Menu.Content className={menu.submenuPanel}>
                  <Menu.Back className={menu.back}>
                    <MenuChevron flipped />
                    Speed
                  </Menu.Back>
                  <Menu.RadioGroup
                    className={menu.group}
                    value={playbackRate.value}
                    onValueChange={playbackRate.setValue}
                    aria-label="Playback rate"
                  >
                    {playbackRate.options.map((option) => (
                      <Menu.RadioItem
                        key={option.value}
                        className={menu.item}
                        value={option.value}
                        disabled={option.disabled}
                      >
                        <span>{option.label}</span>
                        <Menu.ItemIndicator
                          checked={option.value === playbackRate.value}
                          forceMount
                          className={menu.indicator}
                        >
                          <CheckIcon className={icon} />
                        </Menu.ItemIndicator>
                      </Menu.RadioItem>
                    ))}
                  </Menu.RadioGroup>
                </Menu.Content>
              </Menu.Root>
            ) : null}
            {hasCaptions && captions ? (
              <Menu.Root>
                <Menu.Trigger
                  type="captions"
                  className={`${menu.item} media-menu__item--submenu`}
                  render={(props) => (
                    <div {...props}>
                      <span>Captions</span>
                      <span className={menu.hint}>
                        <Menu.ItemValue className={menu.hintLabel} />
                        <MenuChevron />
                      </span>
                    </div>
                  )}
                />
                <Menu.Content className={menu.submenuPanel}>
                  <Menu.Back className={menu.back}>
                    <MenuChevron flipped />
                    Captions
                  </Menu.Back>
                  <Menu.RadioGroup
                    className={menu.group}
                    value={captions.value}
                    onValueChange={captions.setValue}
                    aria-label="Captions"
                  >
                    {captions.options.map((option) => (
                      <Menu.RadioItem
                        key={option.value}
                        className={menu.item}
                        value={option.value}
                        disabled={option.disabled}
                      >
                        <span>{option.label}</span>
                        <Menu.ItemIndicator
                          checked={option.value === captions.value}
                          forceMount
                          className={menu.indicator}
                        >
                          <CheckIcon className={icon} />
                        </Menu.ItemIndicator>
                      </Menu.RadioItem>
                    ))}
                  </Menu.RadioGroup>
                </Menu.Content>
              </Menu.Root>
            ) : null}
          </div>
        </Menu.View>
      </Menu.Content>
    </Menu.Root>
  );
}

export function MinimalVideoSkinTailwind({ children, className, poster: posterProp, ...rest }: MinimalVideoSkinProps): ReactNode {

  return (
    <Container className={`${root(false)} ${className ?? ''}`} {...rest}>
      {children}

      {posterProp && (
        <Poster
          src={isString(posterProp) ? posterProp : undefined}
          render={isRenderProp(posterProp) ? posterProp : undefined}
          className={poster(false)}
        />
      )}

      <BufferingIndicator
        render={(props) => (
          <div {...props} className={bufferingIndicator}>
            <SpinnerIcon className={icon} />
          </div>
        )}
      />

      <ErrorDialog.Root>
        <ErrorDialog.Popup className={error.root}>
          <div className={error.dialog}>
            <div className={error.content}>
              <ErrorDialog.Title className={error.title}>Something went wrong.</ErrorDialog.Title>
              <ErrorDialog.Description className={error.description} />
            </div>
            <div className={error.actions}>
              <ErrorDialog.Close className={`${button.base} ${button.primary}`}>OK</ErrorDialog.Close>
            </div>
          </div>
        </ErrorDialog.Popup>
      </ErrorDialog.Root>

      <Controls.Root
        data-controls="" // Used as a hook for Tailwind has-[] styles
        className={controls}
      >
        <Tooltip.Provider>
          <div className={buttonGroupStart}>
            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <PlayButton className={iconState.play.button} render={<Button />}>
                    <RestartIcon className={`${icon} ${iconState.play.restart}`} />
                    <PlayIcon className={`${icon} ${iconState.play.play}`} />
                    <PauseIcon className={`${icon} ${iconState.play.pause}`} />
                  </PlayButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <SeekButton seconds={-SEEK_TIME} render={<Button />}>
                    <span className={iconContainer}>
                      <SeekIcon className={`${icon} ${iconFlipped}`} />
                      <span className={`${seek.label} ${seek.labelBackward}`}>{SEEK_TIME}</span>
                    </span>
                  </SeekButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <SeekButton seconds={SEEK_TIME} render={<Button />}>
                    <span className={iconContainer}>
                      <SeekIcon className={icon} />
                      <span className={`${seek.label} ${seek.labelForward}`}>{SEEK_TIME}</span>
                    </span>
                  </SeekButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>
          </div>

          <div className={time.controls}>
            <Time.Group className={time.group}>
              <Time.Value type="current" className={time.current} />
              <Time.Separator className={time.separator} />
              <Time.Value type="duration" className={time.duration} />
            </Time.Group>

            <TimeSlider.Root render={<SliderRoot />}>
              <TimeSlider.Track render={<SliderTrack />}>
                <TimeSlider.Fill render={<SliderFill />} />
                <TimeSlider.Buffer render={<SliderBuffer />} />
              </TimeSlider.Track>
              <TimeSlider.Thumb render={<SliderThumb />} />
              <div className={thumbnail.root}>
                <div className={thumbnail.imageWrapper}>
                  <Slider.Thumbnail className={thumbnail.image} />
                </div>
                <TimeSlider.Value type="pointer" className={thumbnail.time} />
                <SpinnerIcon className={`${icon} ${thumbnail.spinner}`} />
              </div>
              <TimeSlider.Preview className={slider.preview}>
                <TimeSlider.Value type="pointer" className={slider.value} />
              </TimeSlider.Preview>
            </TimeSlider.Root>
          </div>

          <div className={buttonGroupEnd}>
            <VolumePopover />

            <SettingsMenu />

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <CastButton className={iconState.cast.button} render={<Button />}>
                    <CastEnterIcon className={`${icon} ${iconState.cast.enter}`} />
                    <CastExitIcon className={`${icon} ${iconState.cast.exit}`} />
                  </CastButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <AirPlayButton className={iconState.airplay.button} render={<Button />}>
                    <AirPlayEnterIcon className={`${icon} ${iconState.airplay.enter}`} />
                    <AirPlayExitIcon className={`${icon} ${iconState.airplay.exit}`} />
                  </AirPlayButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <PiPButton className={iconState.pip.button} render={<Button />}>
                    <PipEnterIcon className={`${icon} ${iconState.pip.off}`} />
                    <PipExitIcon className={`${icon} ${iconState.pip.on}`} />
                  </PiPButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>

            <Tooltip.Root side="top">
              <Tooltip.Trigger
                render={
                  <FullscreenButton className={iconState.fullscreen.button} render={<Button />}>
                    <FullscreenEnterIcon className={`${icon} ${iconState.fullscreen.enter}`} />
                    <FullscreenExitIcon className={`${icon} ${iconState.fullscreen.exit}`} />
                  </FullscreenButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>
          </div>
        </Tooltip.Provider>
      </Controls.Root>

      <div className={overlay} />

      {/* Hotkeys */}
      <Hotkey keys="Space" action="togglePaused" />
      <Hotkey keys="k" action="togglePaused" />
      <Hotkey keys="m" action="toggleMuted" />
      <Hotkey keys="f" action="toggleFullscreen" />
      <Hotkey keys="c" action="toggleSubtitles" />
      <Hotkey keys="i" action="togglePictureInPicture" />
      <Hotkey keys="ArrowRight" action="seekStep" value={SEEK_TIME / 2} />
      <Hotkey keys="ArrowLeft" action="seekStep" value={-(SEEK_TIME / 2)} />
      <Hotkey keys="l" action="seekStep" value={SEEK_TIME} />
      <Hotkey keys="j" action="seekStep" value={-SEEK_TIME} />
      <Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
      <Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
      <Hotkey keys="0-9" action="seekToPercent" />
      <Hotkey keys="Home" action="seekToPercent" value={0} />
      <Hotkey keys="End" action="seekToPercent" value={100} />
      <Hotkey keys=">" action="speedUp" />
      <Hotkey keys="<" action="speedDown" />

      {/* Gestures */}
      <Gesture type="tap" action="togglePaused" pointer="mouse" region="center" />
      <Gesture type="tap" action="toggleControls" pointer="touch" />
      <Gesture type="doubletap" action="seekStep" value={-SEEK_TIME} region="left" />
      <Gesture type="doubletap" action="toggleFullscreen" region="center" />
      <Gesture type="doubletap" action="seekStep" value={SEEK_TIME} region="right" />

      {/* Input Feedback */}
      <StatusAnnouncer />
      <div className={inputFeedback.root}>
        <VolumeIndicator.Root
          className={`${inputFeedback.island.base} ${inputFeedback.island.volume} ${inputFeedback.island.shownVolume}`}
        >
          <VolumeIndicator.Fill data-feedback-island-content="" className={inputFeedback.island.content}>
            <VolumeHighIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeHigh}`} />
            <VolumeLowIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeLow}`} />
            <VolumeOffIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownVolumeOff}`} />
            <div aria-hidden="true" className={inputFeedback.island.volumeProgress} />
            <VolumeIndicator.Value className={inputFeedback.island.value} />
          </VolumeIndicator.Fill>
        </VolumeIndicator.Root>

        <StatusIndicator.Root
          actions={TOP_STATUS_ACTIONS}
          className={`${inputFeedback.island.base} ${inputFeedback.island.shownStatus}`}
        >
          <div className={inputFeedback.island.content}>
            <CaptionsOnIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownCaptionsOn}`} />
            <CaptionsOffIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownCaptionsOff}`} />
            <FullscreenEnterIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownFullscreenEnter}`} />
            <FullscreenExitIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownFullscreenExit}`} />
            <PipEnterIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownPipEnter}`} />
            <PipExitIcon className={`${inputFeedback.island.icon} ${inputFeedback.island.shownPipExit}`} />
            <StatusIndicator.Value className={inputFeedback.island.value} />
          </div>
        </StatusIndicator.Root>

        <SeekIndicator.Root className={inputFeedback.bubble.base}>
          <ChevronIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownSeek}`} />
          <SeekIndicator.Value className={inputFeedback.bubble.time} />
        </SeekIndicator.Root>

        <StatusIndicator.Root actions={CENTER_STATUS_ACTIONS} className={inputFeedback.bubble.base}>
          <PlayIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownPlay}`} />
          <PauseIcon className={`${inputFeedback.bubble.icon} ${inputFeedback.bubble.shownPause}`} />
        </StatusIndicator.Root>
      </div>
    </Container>
  );
}

// ================================================================
// Player
// ================================================================

const SEEK_TIME = 10;

// ================================================================
// Components
// ================================================================

const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
  return (
    <button ref={ref} type="button" className={`${button.base} ${button.subtle} ${button.icon} ${className ?? ''}`} {...props} />
  );
});

const SliderRoot = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderRoot({ className, ...props }, ref) {
  return <div ref={ref} className={`${slider.root} ${className ?? ''}`} {...props} />;
});

const SliderTrack = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderTrack(
  { className, ...props },
  ref
) {
  return <div ref={ref} className={`${slider.track} ${className ?? ''}`} {...props} />;
});

const SliderFill = forwardRef<HTMLDivElement, ComponentProps<'div'> & { type?: 'fill' | 'buffer' }>(function SliderFill(
  { type = 'fill', className, ...props },
  ref
) {
  return (
    <div
      ref={ref}
      className={`${slider.fill.base} ${type === 'fill' ? slider.fill.fill : slider.fill.buffer} ${className ?? ''}`}
      {...props}
    />
  );
});

const SliderBuffer = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderBuffer(props, ref) {
  return <SliderFill type="buffer" ref={ref} {...props} />;
});

const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persistent?: boolean }>(function SliderThumb(
  { persistent, className, ...props },
  ref
) {
  return (
    <div
      ref={ref}
      className={`${slider.thumb.base} ${persistent ? undefined : slider.thumb.interactive} ${className ?? ''}`}
      {...props}
    />
  );
});

function VolumePopover(): ReactNode {
  const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');

  const muteButton = (
    <MuteButton className={iconState.mute.button} render={<Button />}>
      <VolumeOffIcon className={`${icon} ${iconState.mute.volumeOff}`} />
      <VolumeLowIcon className={`${icon} ${iconState.mute.volumeLow}`} />
      <VolumeHighIcon className={`${icon} ${iconState.mute.volumeHigh}`} />
    </MuteButton>
  );

  if (volumeUnsupported) return muteButton;

  return (
    <Popover.Root openOnHover delay={200} closeDelay={100} side="top">
      <Popover.Trigger render={muteButton} />
      <Popover.Popup className={`${popup.volume}`}>
        <VolumeSlider.Root orientation="vertical" thumbAlignment="edge" render={<SliderRoot />}>
          <VolumeSlider.Track render={<SliderTrack />}>
            <VolumeSlider.Fill render={<SliderFill />} />
          </VolumeSlider.Track>
          <VolumeSlider.Thumb render={(props) => <SliderThumb persistent {...props} />} />
        </VolumeSlider.Root>
      </Popover.Popup>
    </Popover.Root>
  );
}

// ================================================================
// Utilities
// ================================================================

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isRenderProp(value: unknown): value is RenderProp<unknown> {
  return typeof value === 'function' || isValidElement(value);
}

Minimal Audio Skin

'use client';

import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { createPlayer, Container, usePlayer, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster, type RenderProp } from '@videojs/react';
import { Audio, audioFeatures } from '@videojs/react/audio';
import './player.css';

function PlaybackRateRadioGroup(): ReactNode {
  const state = usePlaybackRateOptions();
  if (!state) return null;

  const { options, setValue, value } = state;

  return (
    <Menu.RadioGroup className="media-menu__group" value={value} onValueChange={setValue} aria-label="Playback rate">
      {options.map((option) => (
        <Menu.RadioItem key={option.value} className="media-menu__item" value={option.value} disabled={option.disabled}>
          <span>{option.label}</span>
          <Menu.ItemIndicator checked={option.value === value} forceMount className="media-menu__indicator">
            <CheckIcon className="media-icon" />
          </Menu.ItemIndicator>
        </Menu.RadioItem>
      ))}
    </Menu.RadioGroup>
  );
}

function PlaybackRateTrigger(): ReactNode {
  const state = usePlaybackRateOptions();
  if (!state) return null;

  return (
    <Menu.Trigger
      disabled={state.disabled}
      render={<PlaybackRateButton className="media-button--playback-rate" render={<Button />} />}
    />
  );
}

// ================================================================
// Player
// ================================================================

export const Player = createPlayer({ features: audioFeatures });

export interface AudioPlayerProps {
  src: string;
  style?: CSSProperties;
  className?: string;
}

const SEEK_TIME = 10;

/**
 * @example
 * ```tsx
 * <AudioPlayer
 *   src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
 * />
 * ```
 */
export function AudioPlayer({ src, className, ...rest }: AudioPlayerProps): ReactNode {
  return (
    <Player.Provider>
      <Container className={`media-minimal-skin media-minimal-skin--audio ${className ?? ''}`} {...rest}>
        <Audio src={src} />

        <ErrorDialog.Root>
          <ErrorDialog.Popup className="media-error">
            <div className="media-error__dialog">
              <div className="media-error__content">
                <ErrorDialog.Title className="media-error__title">Something went wrong.</ErrorDialog.Title>
                <ErrorDialog.Description className="media-error__description" />
              </div>
              <div className="media-error__actions">
                <ErrorDialog.Close className="media-button media-button--subtle">OK</ErrorDialog.Close>
              </div>
            </div>
          </ErrorDialog.Popup>
        </ErrorDialog.Root>

        <div className="media-controls">
          <Tooltip.Provider>
            <div className="media-button-group">
              <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.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 media-icon--flipped" />
                        <span className="media-icon__label">{SEEK_TIME}</span>
                      </span>
                    </SeekButton>
                  }
                />
                <Tooltip.Popup className="media-tooltip" />
              </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.Root>
            </div>

            <div className="media-time-controls">
              <Time.Group className="media-time-group">
                <Time.Value type="current" className="media-time media-time--current" />
                <Time.Separator className="media-time-separator" />
                <Time.Value type="duration" className="media-time media-time--duration" />
              </Time.Group>

              <TimeSlider.Root className="media-slider">
                <TimeSlider.Track className="media-slider__track">
                  <TimeSlider.Fill className="media-slider__fill" />
                  <TimeSlider.Buffer className="media-slider__buffer" />
                </TimeSlider.Track>
                <TimeSlider.Thumb className="media-slider__thumb" />
                <TimeSlider.Preview className="media-slider__preview">
                  <TimeSlider.Value type="pointer" className="media-slider__value media-time" />
                </TimeSlider.Preview>
              </TimeSlider.Root>
            </div>

            <div className="media-button-group">
              <Menu.Root side="top" align="center" boundary="viewport">
                <PlaybackRateTrigger />
                <Menu.Content className="media-popover media-menu media-menu--playback-rate">
                  <PlaybackRateRadioGroup />
                </Menu.Content>
              </Menu.Root>

              <VolumePopover />
            </div>
          </Tooltip.Provider>
        </div>

        {/* Hotkeys */}
        <Hotkey keys="Space" action="togglePaused" />
        <Hotkey keys="k" action="togglePaused" />
        <Hotkey keys="m" action="toggleMuted" />
        <Hotkey keys="ArrowRight" action="seekStep" value={5} />
        <Hotkey keys="ArrowLeft" action="seekStep" value={-5} />
        <Hotkey keys="l" action="seekStep" value={10} />
        <Hotkey keys="j" action="seekStep" value={-10} />
        <Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
        <Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
        <Hotkey keys="0-9" action="seekToPercent" />
        <Hotkey keys="Home" action="seekToPercent" value={0} />
        <Hotkey keys="End" action="seekToPercent" value={100} />
        <Hotkey keys=">" action="speedUp" />
        <Hotkey keys="<" action="speedDown" />

        {/* Input Feedback */}
        <StatusAnnouncer />
      </Container>

    </Player.Provider>
  );
}

// ================================================================
// Components
// ================================================================

const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
  return (
    <button
      ref={ref}
      type="button"
      className={`media-button media-button--subtle media-button--icon ${className ?? ''}`}
      {...props}
    />
  );
});

function VolumePopover(): ReactNode {
  const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');

  const muteButton = (
    <MuteButton className="media-button--mute" render={<Button />}>
      <VolumeOffIcon className="media-icon media-icon--volume-off" />
      <VolumeLowIcon className="media-icon media-icon--volume-low" />
      <VolumeHighIcon className="media-icon media-icon--volume-high" />
    </MuteButton>
  );

  if (volumeUnsupported) return muteButton;

  return (
    <Popover.Root openOnHover delay={200} closeDelay={100} side="left" boundary="viewport">
      <Popover.Trigger render={muteButton} />
      <Popover.Popup className="media-popover media-popover--volume">
        <VolumeSlider.Root className="media-slider" orientation="horizontal" thumbAlignment="edge">
          <VolumeSlider.Track className="media-slider__track">
            <VolumeSlider.Fill className="media-slider__fill" />
          </VolumeSlider.Track>
          <VolumeSlider.Thumb className="media-slider__thumb media-slider__thumb--persistent" />
        </VolumeSlider.Root>
      </Popover.Popup>
    </Popover.Root>
  );
}
'use client';

import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode } from 'react';
import { CheckIcon, PauseIcon, PlayIcon, RestartIcon, SeekIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons/minimal';
import { createPlayer, Container, usePlayer, ErrorDialog, Hotkey, Menu, MuteButton, PlayButton, usePlaybackRateOptions, PlaybackRateButton, Popover, SeekButton, StatusAnnouncer, Time, TimeSlider, Tooltip, VolumeSlider, type Poster, type RenderProp } from '@videojs/react';
import { Audio, audioFeatures } from '@videojs/react/audio';
import './player.css';

const button = {
  base: "flex items-center justify-center shrink-0 border-none cursor-pointer select-none text-center touch-manipulation min-h-0 py-2 px-4 rounded-lg outline-2 outline-transparent -outline-offset-2 transition-[background-color,outline-offset,scale] will-change-[scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale supports-[corner-shape:squircle]:rounded-[1rem] supports-[corner-shape:squircle]:[corner-shape:squircle] data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden",
  primary: "bg-white text-black font-medium text-shadow-none",
  subtle: "bg-transparent text-inherit text-shadow-inherit hover:bg-current/10 focus-visible:bg-current/10 aria-expanded:bg-current/10",
  icon: "grid w-[2.375rem] aspect-square p-0 active:scale-90",
  live: "inline-flex items-center gap-1.5 aspect-auto w-auto px-3 py-2 text-xs font-semibold uppercase tracking-wider leading-none before:inline-block before:size-2 before:shrink-0 before:rounded-full before:bg-current/40 before:transition-colors before:duration-150 before:ease-out before:content-[\"\"] data-[live-edge]:before:bg-red-500",
};

const buttonGroup = "flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5";

const controls = "peer/controls @container/media-controls flex items-center bg-(--media-controls-background-color) [backdrop-filter:var(--media-controls-backdrop-filter)] text-shadow-2xs text-shadow-(color:--media-current-shadow-color) p-1.5 gap-2 rounded-(--media-border-radius,1rem) peer-data-open/error:**:invisible text-(--media-controls-text-color) ring-1 ring-(color:--media-controls-border-color)";

const error = {
  root: "peer/error group/error hidden data-[open]:flex absolute inset-0 z-20 items-center justify-center outline-none",
  dialog: "absolute inset-0 z-20 flex items-center gap-4 rounded-full px-5 pr-2 bg-(--media-controls-background-color) transition-[opacity,filter,scale] ease-out duration-(--media-error-dialog-transition-duration) delay-(--media-error-dialog-transition-delay) group-data-starting-style/error:opacity-0 group-data-starting-style/error:blur-xs group-data-starting-style/error:scale-95 group-data-ending-style/error:opacity-0 group-data-ending-style/error:blur-xs group-data-ending-style/error:scale-95 group-data-ending-style/error:delay-0",
  content: "flex flex-1 items-center gap-2",
  title: "font-semibold leading-tight",
  description: "opacity-70 wrap-anywhere",
  actions: "flex gap-2 *:flex-1",
};

const icon = "block [grid-area:1/1] size-(--media-icon-size) drop-shadow-[0_1px_0_var(--media-current-shadow-color)] transition-discrete transition-[display,opacity] duration-150 ease-out";

const iconContainer = "relative grid";

const iconFlipped = "[scale:-1_1]";

const iconState = {
  play: {
    button: "group",
    restart: "hidden opacity-0 group-data-ended:block group-data-ended:opacity-100",
    play: "hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100 group-not-data-ended:group-not-data-started:block group-not-data-ended:group-not-data-started:opacity-100",
    pause: "hidden opacity-0 group-data-started:group-not-data-paused:group-not-data-ended:block group-data-started:group-not-data-paused:group-not-data-ended:opacity-100",
  },
  mute: {
    button: "group",
    volumeOff: "hidden opacity-0 group-data-muted:block group-data-muted:opacity-100",
    volumeLow: "hidden opacity-0 group-not-data-muted:group-data-[volume-level=low]:block group-not-data-muted:group-data-[volume-level=low]:opacity-100",
    volumeHigh: "hidden opacity-0 group-not-data-muted:group-not-data-[volume-level=low]:block group-not-data-muted:group-not-data-[volume-level=low]:opacity-100",
  },
  fullscreen: {
    button: "group",
    enter: "hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100",
    exit: "hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100",
  },
  captions: {
    button: "group",
    off: "hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100",
    on: "hidden opacity-0 group-data-active:block group-data-active:opacity-100",
  },
  pip: {
    button: "group",
    off: "hidden opacity-0 group-not-data-pip:block group-not-data-pip:opacity-100",
    on: "hidden opacity-0 group-data-pip:block group-data-pip:opacity-100",
  },
  cast: {
    button: "group",
    enter: "hidden opacity-0 group-not-data-[cast-state=connected]:block group-not-data-[cast-state=connected]:opacity-100",
    exit: "hidden opacity-0 group-data-[cast-state=connected]:block group-data-[cast-state=connected]:opacity-100",
  },
  airplay: {
    button: "group not-data-[airplay-state=connected]:[--media-icon--airplay__fill-animation:none] not-data-[airplay-state=connected]:[--media-icon--airplay__triangle-animation:none]",
    enter: "hidden opacity-0 group-not-data-[airplay-state=connected]:block group-not-data-[airplay-state=connected]:opacity-100",
    exit: "hidden opacity-0 group-data-[airplay-state=connected]:block group-data-[airplay-state=connected]:opacity-100",
  },
};

const menu = {
  root: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-item-height:1.875rem] [--menu-transition-duration:200ms] [--menu-item-transition-duration:100ms] motion-reduce:[--menu-transition-duration:0ms] motion-reduce:[--menu-item-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-(--media-popover-available-height,none) bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] transition-[transform,scale,opacity,filter,width,height] duration-(--menu-transition-duration) ease-in-out box-border rounded-xl p-1 overscroll-none min-w-24 overflow-auto",
  settings: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) [--menu-item-height:1.875rem] [--menu-transition-duration:200ms] [--menu-item-transition-duration:100ms] motion-reduce:[--menu-transition-duration:0ms] motion-reduce:[--menu-item-transition-duration:0ms] max-w-(--media-popover-available-width,none) max-h-(--media-popover-available-height,none) bg-(--media-popover-background-color) [backdrop-filter:var(--media-popover-backdrop-filter)] shadow-[0_0_0_1px_var(--media-popover-border-color),0_4px_6px_-1px_oklch(0_0_0/0.1),0_2px_4px_-2px_oklch(0_0_0/0.1)] transition-[transform,scale,opacity,filter,width,height] duration-(--menu-transition-duration) ease-in-out box-border rounded-xl p-1 overscroll-none [--menu-transition-duration:250ms] relative min-w-44 w-(--media-menu-width) h-(--media-menu-height) overflow-hidden",
  group: "flex flex-col gap-0.5",
  item: "flex h-(--menu-item-height) cursor-pointer select-none items-center rounded-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-[background-color,color] duration-(--menu-item-transition-duration) ease-out hover:bg-current/10 data-highlighted:bg-current/10 focus-visible:outline-current focus-visible:outline-offset-2 group/menu-item justify-between gap-2 px-2.5 tabular-nums text-inherit data-[availability=unavailable]:hidden data-[availability=unsupported]:hidden aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50",
  indicator: "-mr-1 shrink-0 opacity-0 group-aria-checked/menu-item:opacity-100",
  rootView: "absolute inset-0 overflow-auto overscroll-none p-1 outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate] data-[menu-view-state=inactive]:-translate-x-full data-[menu-view-state=inactive]:blur",
  submenuPanel: "absolute inset-0 overflow-auto overscroll-none p-1 outline-none translate-none data-starting-style:overflow-hidden data-ending-style:overflow-hidden transition-[translate,filter] duration-(--menu-transition-duration) ease-in-out will-change-[translate] z-10 not-data-open:not-data-ending-style:-translate-x-full not-data-open:not-data-ending-style:transition-none data-starting-style:pointer-events-none data-ending-style:pointer-events-none data-starting-style:blur data-ending-style:blur data-starting-style:data-[direction=forward]:translate-x-full data-ending-style:data-[direction=forward]:-translate-x-full data-starting-style:data-[direction=back]:-translate-x-full data-ending-style:data-[direction=back]:translate-x-full",
  back: "flex h-(--menu-item-height) cursor-pointer select-none items-center rounded-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) outline-2 -outline-offset-2 outline-transparent transition-[background-color,color] duration-(--menu-item-transition-duration) ease-out hover:bg-current/10 data-highlighted:bg-current/10 focus-visible:outline-current focus-visible:outline-offset-2 mb-0.5 w-full gap-1.5 px-2.5 font-medium text-current/70 hover:text-inherit data-highlighted:text-inherit focus-visible:text-inherit",
  hint: "ml-auto flex min-w-0 items-center gap-1 text-xs text-current/65",
  hintLabel: "max-w-24 overflow-hidden text-ellipsis whitespace-nowrap",
  chevron: "size-3.5 first:-ml-1 last:-mr-1",
  settingsGroup: "group/settings",
  settingsTrigger: "group hidden group-has-[[data-availability=available]]/settings:grid",
  settingsIcon: "transition-transform duration-150 ease-in-out group-aria-expanded:rotate-90 motion-reduce:duration-0",
};

const playbackRate = {
  button: "tabular-nums after:w-[4ch] after:content-[attr(data-rate)_'×'] data-[inline-rate-label]:after:content-none",
};

const popup = {
  base: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full",
  popover: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset)",
  tooltip: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full px-2 py-1 rounded-lg text-[0.75rem] whitespace-nowrap bg-(--media-tooltip-background-color) [backdrop-filter:var(--media-tooltip-backdrop-filter)] ring-1 ring-(color:--media-tooltip-border-color) shadow-md shadow-black/10 text-(--media-tooltip-text-color) data-[side=top]:before:h-(--media-tooltip-side-offset) data-[side=bottom]:before:h-(--media-tooltip-side-offset) data-[side=left]:before:w-(--media-tooltip-side-offset) data-[side=right]:before:w-(--media-tooltip-side-offset)",
  volume: "m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-(--media-popup-transition-duration) ease-(--media-popup-transition-timing-function) data-starting-style:opacity-0 data-starting-style:scale-50 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-50 data-ending-style:blur-sm data-instant:duration-0 data-[side=top]:origin-bottom data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left before:absolute before:pointer-events-[inherit] data-[side=top]:before:left-0 data-[side=top]:before:right-0 data-[side=top]:before:top-full data-[side=bottom]:before:left-0 data-[side=bottom]:before:right-0 data-[side=bottom]:before:bottom-full data-[side=left]:before:top-0 data-[side=left]:before:bottom-0 data-[side=left]:before:left-full data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:right-full data-[side=top]:before:h-(--media-popover-side-offset) data-[side=bottom]:before:h-(--media-popover-side-offset) data-[side=left]:before:w-(--media-popover-side-offset) data-[side=right]:before:w-(--media-popover-side-offset) p-0 pl-16 bg-transparent bg-gradient-to-l from-(--media-controls-background-color) from-80% to-transparent",
};

const root = "**:box-border [&_[hidden][hidden]]:hidden [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] block relative isolate h-full w-full @container/media-root rounded-(--media-border-radius,0.75rem) font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased outline-2 outline-transparent -outline-offset-4 transition-[outline-offset,outline-color] duration-100 ease-out focus-visible:outline-current focus-visible:outline-offset-2 [--media-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.15))] [--media-current-shadow-color-subtle:oklch(from_var(--media-current-shadow-color)_l_c_h/calc(alpha*0.4))] [--media-icon-size:18px] [--media-controls-background-color:oklch(1_0_0)] [--media-controls-backdrop-filter:blur(16px)_saturate(1.5)] [--media-controls-border-color:oklch(0_0_0/0.05)] [--media-controls-text-color:var(--media-color-primary,oklch(0_0_0))] [--media-error-dialog-transition-duration:250ms] [--media-error-dialog-transition-delay:100ms] [--media-popup-transition-duration:100ms] [--media-popup-transition-timing-function:ease-out] [--media-tooltip-background-color:oklch(1_0_0/0.1)] [--media-tooltip-border-color:oklch(0_0_0/0.05)] [--media-tooltip-backdrop-filter:blur(16px)_saturate(1.5)] [--media-tooltip-text-color:currentColor] [--media-tooltip-side-offset:0.75rem] [--media-tooltip-boundary-offset:0.75rem] [--media-popover-background-color:oklch(1_0_0/0.1)] [--media-popover-border-color:oklch(0_0_0/0.05)] [--media-popover-backdrop-filter:blur(16px)_saturate(1.5)] [--media-popover-side-offset:0.75rem] [--media-popover-boundary-offset:0.75rem] motion-reduce:[--media-error-dialog-transition-duration:50ms] motion-reduce:[--media-error-dialog-transition-delay:0ms] motion-reduce:[--media-popup-transition-duration:0ms] dark:[--media-controls-background-color:oklch(0_0_0)] dark:[--media-controls-border-color:oklch(1_0_0/0.1)] dark:[--media-controls-text-color:var(--media-color-primary,oklch(1_0_0))] [@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(1_0_0)] contrast-more:[--media-tooltip-background-color:oklch(1_0_0)] dark:[@media(prefers-reduced-transparency:reduce)]:[--media-tooltip-background-color:oklch(0_0_0)] dark:contrast-more:[--media-tooltip-background-color:oklch(0_0_0)]";

const seek = {
  label: "text-[10px] font-medium tracking-tighter tabular-nums",
  labelForward: "absolute -right-px -bottom-0.75",
  labelBackward: "absolute -left-px -bottom-0.75",
};

const slider = {
  root: "group/slider relative flex flex-1 items-center justify-center rounded-full outline-none cursor-pointer data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8 data-[orientation=vertical]:h-[4.5rem]",
  track: "relative isolate overflow-hidden bg-current/20 rounded-[inherit] select-none data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-0.75 data-[orientation=vertical]:w-0.75 data-[orientation=vertical]:h-full",
  fill: {
    base: "absolute rounded-[inherit] pointer-events-none",
    fill: "bg-current data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:w-(--media-slider-fill,0) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:h-(--media-slider-fill,0)",
    buffer: "bg-current/20 duration-250 ease-out data-[orientation=horizontal]:inset-y-0 data-[orientation=horizontal]:left-0 data-[orientation=horizontal]:transition-[width] data-[orientation=horizontal]:w-(--media-slider-buffer,0) data-[orientation=vertical]:inset-x-0 data-[orientation=vertical]:bottom-0 data-[orientation=vertical]:transition-[height] data-[orientation=vertical]:h-(--media-slider-buffer)",
  },
  thumb: {
    base: "z-10 absolute size-3 -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-current-shadow-color,oklch(0_0_0/0.15)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,scale,outline-offset] duration-150 ease-out select-none outline-2 outline-transparent -outline-offset-2 focus-visible:outline-current focus-visible:outline-offset-2 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:left-(--media-slider-fill,0) data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:top-[calc(100%-var(--media-slider-fill,0))]",
    interactive: "opacity-0 scale-70 origin-center group-hover/slider:opacity-100 group-hover/slider:scale-100 group-focus-within/slider:opacity-100 group-focus-within/slider:scale-100",
  },
  preview: "group/preview before:block before:bg-current/35 before:opacity-0 before:scale-50 before:transition-[opacity,scale] before:duration-200 before:ease-out data-pointing:not-data-dragging:before:opacity-100 data-pointing:not-data-dragging:before:scale-100 data-[orientation=horizontal]:before:min-w-px data-[orientation=horizontal]:before:h-5 data-[orientation=vertical]:before:w-5 data-[orientation=vertical]:before:min-h-px peer-has-[[role=img]:not([data-hidden])]/thumbnail:*:hidden",
  value: "absolute bottom-6 tabular-nums -translate-x-1/2 translate-y-2 scale-50 opacity-0 blur-lg text-shadow-2xs text-shadow-(color:--media-current-shadow-color) transition-[filter,opacity,scale,translate] duration-200 ease-out group-data-pointing/preview:translate-y-0 group-data-pointing/preview:scale-100 group-data-pointing/preview:opacity-100 group-data-pointing/preview:blur-none bottom-10",
};

const time = {
  group: "flex items-center gap-1",
  current: "hidden tabular-nums @2xl/media-root:inline",
  separator: "hidden @2xl/media-root:inline @2xl/media-root:text-current/60",
  duration: "tabular-nums @2xl/media-root:text-current/60",
  controls: "@container flex flex-row-reverse items-center flex-1 gap-3 @2xl/media-root:flex-row",
};

interface MinimalAudioSkinProps {
  src: string;
  style?: CSSProperties;
  className?: string;
}

function PlaybackRateRadioGroup(): ReactNode {
  const state = usePlaybackRateOptions();
  if (!state) return null;

  const { options, setValue, value } = state;

  return (
    <Menu.RadioGroup className={menu.group} value={value} onValueChange={setValue} aria-label="Playback rate">
      {options.map((option) => (
        <Menu.RadioItem key={option.value} className={menu.item} value={option.value} disabled={option.disabled}>
          <span>{option.label}</span>
          <Menu.ItemIndicator checked={option.value === value} forceMount className={menu.indicator}>
            <CheckIcon className={icon} />
          </Menu.ItemIndicator>
        </Menu.RadioItem>
      ))}
    </Menu.RadioGroup>
  );
}

function PlaybackRateTrigger(): ReactNode {
  const state = usePlaybackRateOptions();
  if (!state) return null;

  return (
    <Menu.Trigger
      disabled={state.disabled}
      render={<PlaybackRateButton className={playbackRate.button} render={<Button />} />}
    />
  );
}

export function MinimalAudioSkinTailwind({ children, className, ...rest }: MinimalAudioSkinProps): ReactNode {

  return (
    <Container className={`${root} ${className ?? ''}`} {...rest}>
      {children}

      <ErrorDialog.Root>
        <ErrorDialog.Popup className={error.root}>
          <div className={error.dialog}>
            <div className={error.content}>
              <ErrorDialog.Title className={error.title}>Something went wrong.</ErrorDialog.Title>
              <ErrorDialog.Description className={error.description} />
            </div>
            <div className={error.actions}>
              <ErrorDialog.Close className={`${button.base} ${button.subtle}`}>OK</ErrorDialog.Close>
            </div>
          </div>
        </ErrorDialog.Popup>
      </ErrorDialog.Root>

      <div className={controls}>
        <Tooltip.Provider>
          <div className={buttonGroup}>
            <Tooltip.Root side="top" boundary="viewport">
              <Tooltip.Trigger
                render={
                  <PlayButton className={iconState.play.button} render={<Button />}>
                    <RestartIcon className={`${icon} ${iconState.play.restart}`} />
                    <PlayIcon className={`${icon} ${iconState.play.play}`} />
                    <PauseIcon className={`${icon} ${iconState.play.pause}`} />
                  </PlayButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`}></Tooltip.Popup>
            </Tooltip.Root>

            <Tooltip.Root side="top" boundary="viewport">
              <Tooltip.Trigger
                render={
                  <SeekButton seconds={-SEEK_TIME} render={<Button />}>
                    <span className={iconContainer}>
                      <SeekIcon className={`${icon} ${iconFlipped}`} />
                      <span className={`${seek.label} ${seek.labelBackward}`}>{SEEK_TIME}</span>
                    </span>
                  </SeekButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>

            <Tooltip.Root side="top" boundary="viewport">
              <Tooltip.Trigger
                render={
                  <SeekButton seconds={SEEK_TIME} render={<Button />}>
                    <span className={iconContainer}>
                      <SeekIcon className={icon} />
                      <span className={`${seek.label} ${seek.labelForward}`}>{SEEK_TIME}</span>
                    </span>
                  </SeekButton>
                }
              />
              <Tooltip.Popup className={`${popup.tooltip}`} />
            </Tooltip.Root>
          </div>

          <div className={time.controls}>
            <Time.Group className={time.group}>
              <Time.Value type="current" className={time.current} />
              <Time.Separator className={time.separator} />
              <Time.Value type="duration" className={time.duration} />
            </Time.Group>

            <TimeSlider.Root render={<SliderRoot />}>
              <TimeSlider.Track render={<SliderTrack />}>
                <TimeSlider.Fill render={<SliderFill />} />
                <TimeSlider.Buffer render={<SliderBuffer />} />
              </TimeSlider.Track>
              <TimeSlider.Thumb render={<SliderThumb />} />
              <TimeSlider.Preview className={slider.preview}>
                <TimeSlider.Value type="pointer" className={slider.value} />
              </TimeSlider.Preview>
            </TimeSlider.Root>
          </div>

          <div className={buttonGroup}>
            <Menu.Root side="top" align="center" boundary="viewport">
              <PlaybackRateTrigger />
              <Menu.Content className={`${popup.popover} ${menu.root}`}>
                <PlaybackRateRadioGroup />
              </Menu.Content>
            </Menu.Root>

            <VolumePopover />
          </div>
        </Tooltip.Provider>
      </div>
    </Container>
  );
}

// ================================================================
// Player
// ================================================================

const SEEK_TIME = 10;

// ================================================================
// Components
// ================================================================

const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
  return (
    <button ref={ref} type="button" className={`${button.base} ${button.subtle} ${button.icon} ${className ?? ''}`} {...props} />
  );
});

const SliderRoot = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderRoot({ className, ...props }, ref) {
  return <div ref={ref} className={`${slider.root} ${className ?? ''}`} {...props} />;
});

const SliderTrack = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderTrack(
  { className, ...props },
  ref
) {
  return <div ref={ref} className={`${slider.track} ${className ?? ''}`} {...props} />;
});

const SliderFill = forwardRef<HTMLDivElement, ComponentProps<'div'> & { type?: 'fill' | 'buffer' }>(function SliderFill(
  { type = 'fill', className, ...props },
  ref
) {
  return (
    <div
      ref={ref}
      className={`${slider.fill.base} ${type === 'fill' ? slider.fill.fill : slider.fill.buffer} ${className ?? ''}`}
      {...props}
    />
  );
});

const SliderBuffer = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderBuffer(props, ref) {
  return <SliderFill type="buffer" ref={ref} {...props} />;
});

const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persistent?: boolean }>(function SliderThumb(
  { persistent, className, ...props },
  ref
) {
  return (
    <div
      ref={ref}
      className={`${slider.thumb.base} ${persistent ? undefined : slider.thumb.interactive} ${className ?? ''}`}
      {...props}
    />
  );
});

function VolumePopover(): ReactNode {
  const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');

  const muteButton = (
    <MuteButton className={iconState.mute.button} render={<Button />}>
      <VolumeOffIcon className={`${icon} ${iconState.mute.volumeOff}`} />
      <VolumeLowIcon className={`${icon} ${iconState.mute.volumeLow}`} />
      <VolumeHighIcon className={`${icon} ${iconState.mute.volumeHigh}`} />
    </MuteButton>
  );

  if (volumeUnsupported) return muteButton;

  return (
    <Popover.Root openOnHover delay={200} closeDelay={100} side="left" boundary="viewport">
      <Popover.Trigger render={muteButton} />
      <Popover.Popup className={`${popup.volume}`}>
        <VolumeSlider.Root orientation="horizontal" thumbAlignment="edge" render={<SliderRoot />}>
          <VolumeSlider.Track render={<SliderTrack />}>
            <VolumeSlider.Fill render={<SliderFill />} />
          </VolumeSlider.Track>
          <VolumeSlider.Thumb render={(props) => <SliderThumb persistent {...props} />} />
        </VolumeSlider.Root>
      </Popover.Popup>
    </Popover.Root>
  );
}