diff --git a/site/CLAUDE.md b/site/CLAUDE.md index 3ad0258f..ed6742c5 100644 --- a/site/CLAUDE.md +++ b/site/CLAUDE.md @@ -421,9 +421,16 @@ All content must be written in **MDX format** to support: authors: string[]; // Reference to authors.json canonical?: string; // Canonical URL override devOnly?: boolean; // Show only in development + ogImage?: ImageMetadata | string; // Local image or external URL + twitterImage?: ImageMetadata | string; // Falls back to ogImage } ``` +**OG images** go in `src/assets/blog/{date-slug}/og.png`. Reference from frontmatter with a relative path: +```yaml +ogImage: '../../assets/blog/2026-03-10-my-post/og.png' +``` + ### Docs Collection (`src/content/docs/`) **Subdirectories:** diff --git a/site/astro.config.mjs b/site/astro.config.mjs index ec92062e..7ab81ced 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -18,7 +18,14 @@ import remarkConditionalHeadings from './src/utils/remarkConditionalHeadings'; import { remarkReadingTime } from './src/utils/remarkReadingTime.mjs'; import shikiTransformMetadata from './src/utils/shikiTransformMetadata'; -const SITE_URL = 'https://videojs.org'; +// Astro docs say `site` should be "your final, deployed URL", but we override +// it per-deploy so that generated absolute URLs (OG images, RSS, etc.) resolve +// correctly on Netlify deploy previews. On production, DEPLOY_PRIME_URL is the +// primary site URL so it's equivalent. +// +// For URLs that must always point to production regardless of deploy context +// (e.g. canonical, JSON-LD), use PRODUCTION_URL from src/consts.ts instead. +const SITE_URL = process.env.DEPLOY_PRIME_URL || 'https://videojs.org'; // https://astro.build/config export default defineConfig({ diff --git a/site/src/assets/blog/2026-03-10-videojs-v10-beta-hello-world-again/og.png b/site/src/assets/blog/2026-03-10-videojs-v10-beta-hello-world-again/og.png new file mode 100644 index 00000000..eed69da0 Binary files /dev/null and b/site/src/assets/blog/2026-03-10-videojs-v10-beta-hello-world-again/og.png differ diff --git a/site/src/assets/blog/2099-12-31-blog-component-test/og.png b/site/src/assets/blog/2099-12-31-blog-component-test/og.png new file mode 100644 index 00000000..eed69da0 Binary files /dev/null and b/site/src/assets/blog/2099-12-31-blog-component-test/og.png differ diff --git a/site/src/consts.ts b/site/src/consts.ts index 2aed7aed..8d836072 100644 --- a/site/src/consts.ts +++ b/site/src/consts.ts @@ -1,3 +1,7 @@ +// Always https://videojs.org. Unlike Astro.site, which varies per deploy +// (e.g. deploy preview URLs), this is stable for canonical URLs and other +// references that must always point to production. +export const PRODUCTION_URL = new URL('https://videojs.org'); export const SITE_TITLE = 'Video.js'; export const SEO_SUFFIX = 'Open Source Video Player'; export const SITE_DESCRIPTION = `The open-source video player for React and HTML. Lightweight, accessible components built for performance and streaming.`; diff --git a/site/src/content.config.ts b/site/src/content.config.ts index 9195704e..8833ca9a 100644 --- a/site/src/content.config.ts +++ b/site/src/content.config.ts @@ -49,7 +49,7 @@ const blog = defineCollection({ }, }), // Type-check frontmatter using a schema - schema: () => + schema: ({ image }) => z.object({ title: z.string(), description: z.string(), @@ -58,6 +58,8 @@ const blog = defineCollection({ authors: z.array(reference('authors')), canonical: z.string().url().optional(), devOnly: z.boolean().optional(), // only visible in development mode + ogImage: image().or(z.string().url()).optional(), + twitterImage: image().or(z.string().url()).optional(), }), }); diff --git a/site/src/content/blog/2026-03-10-videojs-v10-beta-hello-world-again.mdx b/site/src/content/blog/2026-03-10-videojs-v10-beta-hello-world-again.mdx index bde66565..2d589896 100644 --- a/site/src/content/blog/2026-03-10-videojs-v10-beta-hello-world-again.mdx +++ b/site/src/content/blog/2026-03-10-videojs-v10-beta-hello-world-again.mdx @@ -2,6 +2,7 @@ title: 'Video.js v10 Beta: Hello, World (again)' description: "Video.js v10 is a ground-up rewrite combining four open source players into one modern framework — 88% smaller default bundles, first-class React and TypeScript support, composable architecture, and beautiful new skins designed by Plyr's creator." authors: [steve-heffernan] +ogImage: '../../assets/blog/2026-03-10-videojs-v10-beta-hello-world-again/og.png' --- Today we're excited to release the Video.js v10.0.0 beta. It's the result of a rather large ground-up rewrite, not just of Video.js ([discussion](https://github.com/videojs/video.js/discussions/9035)) but also of [Plyr](https://github.com/sampotts/plyr/discussions/2871), [Vidstack](https://github.com/vidstack/player/discussions/1747), and [Media Chrome](https://www.mux.com/blog/from-media-chrome-to-video-js-v10-the-evolution-of-html-first-video-players), through a rare teaming-up of open source projects and people who care a lot about web video, with a combined 75,000 github stars and tens of billions of video plays monthly. diff --git a/site/src/content/blog/2099-12-31-blog-component-test.mdx b/site/src/content/blog/2099-12-31-blog-component-test.mdx index 36398507..652fefe6 100644 --- a/site/src/content/blog/2099-12-31-blog-component-test.mdx +++ b/site/src/content/blog/2099-12-31-blog-component-test.mdx @@ -3,6 +3,7 @@ title: 'Blog Component Test' description: 'A dev-only blog post for testing blog components and MDX features' authors: [darius-cepulis] devOnly: true +ogImage: '../../assets/blog/2099-12-31-blog-component-test/og.png' --- import Aside from '@/components/Aside.astro' diff --git a/site/src/content/ejected-skins.json b/site/src/content/ejected-skins.json deleted file mode 100644 index 6cfff8c6..00000000 --- a/site/src/content/ejected-skins.json +++ /dev/null @@ -1,130 +0,0 @@ -[ - { - "id": "default-video", - "name": "Default Video", - "platform": "html", - "style": "css", - "html": "\n \n\n \n
\n \n
\n
\n\n \n\n \n \n \n \n \n \n \n Replay\n Play\n Pause\n \n\n \n \n \n 10\n \n \n \n Seek backward 10 seconds\n \n\n \n \n \n 10\n \n \n \n Seek forward 10 seconds\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n Toggle playback rate\n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n Enable captions\n Disable captions\n \n\n \n \n \n \n Enter picture-in-picture\n Exit picture-in-picture\n \n\n \n \n \n \n \n Enter fullscreen\n Exit fullscreen\n \n \n\n
\n
", - "css": "/* ==========================================================================\n Icon State Visibility for Video Skins\n\n Data-attribute-driven visibility rules for multi-state icon buttons.\n Uses :is() with both element selectors (for HTML custom element wrappers)\n and class selectors (for React rendered SVG elements).\n ========================================================================== */\n\n/* --- All icons hidden by default --- */\n\n.media-button--play .media-icon--restart,\n.media-button--play .media-icon--play,\n.media-button--play .media-icon--pause,\n.media-button--mute .media-icon--volume-off,\n.media-button--mute .media-icon--volume-low,\n.media-button--mute .media-icon--volume-high,\n.media-button--fullscreen .media-icon--fullscreen-enter,\n.media-button--fullscreen .media-icon--fullscreen-exit,\n.media-button--captions .media-icon--captions-off,\n.media-button--captions .media-icon--captions-on {\n display: none;\n opacity: 0;\n}\n\n/* --- Active icon per state --- */\n\n/* Play: ended → restart */\n.media-button--play[data-ended] .media-icon--restart,\n/* Play: paused (not ended) → play */\n.media-button--play:not([data-ended])[data-paused] .media-icon--play,\n/* Play: playing (not paused, not ended) → pause */\n.media-button--play:not([data-paused]):not([data-ended]) .media-icon--pause,\n/* Mute: muted → volume off */\n.media-button--mute[data-muted] .media-icon--volume-off,\n/* Mute: volume low (not muted) → volume low */\n.media-button--mute:not([data-muted])[data-volume-level=\"low\"] .media-icon--volume-low,\n/* Mute: volume high (not muted, not low) → volume high */\n.media-button--mute:not([data-muted]):not([data-volume-level=\"low\"]) .media-icon--volume-high,\n/* Fullscreen: not fullscreen → enter */\n.media-button--fullscreen:not([data-fullscreen]) .media-icon--fullscreen-enter,\n/* Fullscreen: fullscreen → exit */\n.media-button--fullscreen[data-fullscreen] .media-icon--fullscreen-exit,\n/* Captions: not active → captions off */\n.media-button--captions:not([data-active]) .media-icon--captions-off,\n/* Captions: active → captions on */\n.media-button--captions[data-active] .media-icon--captions-on {\n display: block;\n opacity: 1;\n}\n\n/* ==========================================================================\n Tooltip Label State Visibility\n\n Data-attribute-driven visibility rules for multi-state tooltip labels.\n Uses adjacent sibling selectors to match button state → tooltip content.\n ========================================================================== */\n\n/* --- All multi-state labels hidden by default --- */\n\n.media-tooltip-label {\n display: none;\n}\n\n/* --- Active label per state --- */\n\n/* Play: ended → replay */\n.media-button--play[data-ended] + media-tooltip .media-tooltip-label--replay,\n/* Play: paused (not ended) → play */\n .media-button--play:not([data-ended])[data-paused] + media-tooltip\n .media-tooltip-label--play,\n/* Play: playing (not paused, not ended) → pause */\n .media-button--play:not([data-paused]):not([data-ended]) + media-tooltip\n .media-tooltip-label--pause,\n/* Fullscreen: not fullscreen → enter */\n .media-button--fullscreen:not([data-fullscreen]) + media-tooltip\n .media-tooltip-label--enter-fullscreen,\n/* Fullscreen: fullscreen → exit */\n .media-button--fullscreen[data-fullscreen] + media-tooltip\n .media-tooltip-label--exit-fullscreen,\n/* Captions: not active → enable */\n .media-button--captions:not([data-active]) + media-tooltip\n .media-tooltip-label--enable-captions,\n/* Captions: active → disable */\n .media-button--captions[data-active] + media-tooltip\n .media-tooltip-label--disable-captions,\n/* PiP: not in pip → enter */\n media-pip-button:not([data-pip]) + media-tooltip\n .media-tooltip-label--enter-pip,\n/* PiP: in pip → exit */\n media-pip-button[data-pip] + media-tooltip\n .media-tooltip-label--exit-pip {\n display: block;\n}\n\n/* ==========================================================================\n Reset\n ========================================================================== */\n\n.media-default-skin *,\n.media-default-skin *::before,\n.media-default-skin *::after {\n box-sizing: border-box;\n margin: 0;\n}\n.media-default-skin img,\n.media-default-skin video,\n.media-default-skin svg {\n display: block;\n max-width: 100%;\n}\n.media-default-skin button {\n font: inherit;\n}\n@media (prefers-reduced-motion: no-preference) {\n .media-default-skin {\n interpolate-size: allow-keywords;\n }\n}\n\n/* ==========================================================================\n Root Container\n ========================================================================== */\n\n.media-default-skin {\n position: relative;\n isolation: isolate;\n display: block;\n container: media-root / inline-size;\n border-radius: var(--media-border-radius, 2rem);\n font-family:\n Inter Variable,\n Inter,\n ui-sans-serif,\n system-ui,\n sans-serif;\n font-size: 0.8125rem;\n line-height: 1.5;\n letter-spacing: normal;\n -webkit-font-smoothing: auto;\n -moz-osx-font-smoothing: auto;\n\n &:fullscreen {\n border-radius: 0;\n }\n}\n\n/* ==========================================================================\n Surface (shared glass effect for tooltips, popovers, controls)\n ========================================================================== */\n\n.media-default-skin .media-surface {\n background-color: var(--media-surface-background-color);\n backdrop-filter: var(--media-surface-backdrop-filter);\n box-shadow:\n inset 0 0 0 1px var(--media-surface-inner-border-color),\n 0 1px 3px 0 var(--media-surface-shadow-color),\n 0 1px 2px -1px var(--media-surface-shadow-color);\n\n /* Inner border ring */\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n z-index: 10;\n border-radius: inherit;\n box-shadow: 0 0 0 1px var(--media-surface-outer-border-color);\n pointer-events: none;\n }\n\n @media (prefers-reduced-transparency: reduce) {\n background-color: oklch(from var(--media-surface-background-color) l c h / 0.7);\n }\n\n @media (prefers-contrast: more) {\n background-color: oklch(from var(--media-surface-background-color) l c h / 0.9);\n }\n}\n\n/* ==========================================================================\n Media Element\n ========================================================================== */\n\n.media-default-skin ::slotted(video),\n.media-default-skin video {\n display: block;\n width: 100%;\n height: 100%;\n border-radius: var(--media-border-radius, 2rem);\n}\n\n/* ==========================================================================\n Poster Image\n ========================================================================== */\n\n.media-default-skin > img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n border-radius: inherit;\n object-fit: cover;\n transition: opacity 0.25s;\n pointer-events: none;\n\n &:not([data-visible]) {\n opacity: 0;\n }\n}\n\n/* ==========================================================================\n Overlay / Scrim\n ========================================================================== */\n\n.media-default-skin .media-overlay {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.3), oklch(0 0 0 / 0));\n backdrop-filter: blur(0) saturate(1.2) brightness(0.9);\n opacity: 0;\n transition-property: opacity, backdrop-filter;\n transition-duration: 300ms;\n transition-delay: 500ms;\n transition-timing-function: ease-out;\n pointer-events: none;\n\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n }\n}\n\n.media-default-skin .media-controls[data-visible] ~ .media-overlay,\n.media-default-skin .media-error[data-open] ~ .media-overlay {\n opacity: 1;\n transition-duration: 150ms;\n transition-delay: 0ms;\n}\n\n.media-default-skin .media-error[data-open] ~ .media-overlay {\n backdrop-filter: blur(8px) saturate(1.2) brightness(0.9);\n}\n\n/* ==========================================================================\n Buffering Indicator\n ========================================================================== */\n\n.media-default-skin .media-buffering-indicator {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n color: oklch(1 0 0);\n pointer-events: none;\n\n &[data-visible] {\n display: flex;\n }\n\n .media-surface {\n padding: 0.25rem;\n border-radius: 100%;\n }\n}\n\n/* ==========================================================================\n Error Dialog\n ========================================================================== */\n\n.media-default-skin .media-error {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.media-default-skin .media-error__dialog {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n max-width: 18rem;\n padding: 0.75rem;\n border-radius: 1.75rem;\n color: oklch(1 0 0);\n font-size: 0.875rem;\n transition-property: opacity, transform;\n transition-duration: 500ms;\n transition-delay: 100ms;\n transition-timing-function: linear(\n 0,\n 0.034 1.5%,\n 0.763 9.7%,\n 1.066 13.9%,\n 1.198 19.9%,\n 1.184 21.8%,\n 0.963 37.5%,\n 0.997 50.9%,\n 1\n );\n\n /* Simple, fast transition for reduced motion users */\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n }\n}\n\n.media-default-skin .media-error[data-starting-style] .media-error__dialog,\n.media-default-skin .media-error[data-ending-style] .media-error__dialog {\n opacity: 0;\n transform: scale(0.5);\n}\n\n.media-default-skin .media-error__content {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n padding: 0.5rem 0.5rem 0.375rem;\n}\n\n.media-default-skin .media-error__title {\n font-weight: 600;\n line-height: 1.25;\n}\n\n.media-default-skin .media-error__description {\n opacity: 0.7;\n}\n\n.media-default-skin .media-error__actions {\n display: flex;\n gap: 0.5rem;\n\n & > * {\n flex: 1;\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-default-skin .media-controls {\n container: media-controls / inline-size;\n display: flex;\n align-items: center;\n gap: 0.075rem;\n padding: 0.175rem;\n border-radius: calc(infinity * 1px);\n --media-controls-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.25));\n --media-controls-current-shadow-color-subtle: oklch(\n from var(--media-controls-current-shadow-color) l c h /\n calc(alpha * 0.4)\n );\n text-shadow: 0 0 1px var(--media-controls-current-shadow-color);\n\n @container media-root (width > 40rem) {\n gap: 0.125rem;\n padding: 0.25rem;\n }\n}\n\n/* ==========================================================================\n Time Display\n ========================================================================== */\n\n.media-default-skin .media-time {\n container: media-time / inline-size;\n display: flex;\n align-items: center;\n flex: 1;\n gap: 0.75rem;\n padding-inline: 0.5rem;\n\n & .media-time__value:first-child {\n display: none;\n\n @container media-time (width > 18rem) {\n display: block;\n }\n }\n}\n\n.media-default-skin .media-time__value {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Buttons\n ========================================================================== */\n\n/* Base button */\n.media-default-skin .media-button {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0.5rem 1rem;\n background: oklch(1 0 0);\n border: none;\n border-radius: calc(infinity * 1px);\n outline: 2px solid transparent;\n outline-offset: -2px;\n color: oklch(0 0 0);\n font-weight: 500;\n text-align: center;\n transition-property: background-color, color, outline-offset, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n cursor: pointer;\n user-select: none;\n\n &:focus-visible {\n outline-color: oklch(62.3% 0.214 259.815);\n outline-offset: 2px;\n }\n\n &[disabled] {\n opacity: 0.5;\n filter: grayscale(1);\n cursor: not-allowed;\n }\n\n &[data-availability=\"unavailable\"] {\n display: none;\n }\n}\n\n/* Icon button variant */\n.media-default-skin .media-button--icon {\n display: grid;\n width: 2.125rem;\n padding: 0;\n aspect-ratio: 1;\n background: transparent;\n color: inherit;\n text-shadow: inherit;\n\n &:hover,\n &:focus-visible,\n &[aria-expanded=\"true\"] {\n background-color: oklch(from currentColor l c h / 0.1);\n text-decoration: none;\n }\n\n &:active {\n transform: scale(0.9);\n }\n\n & .media-icon {\n filter: drop-shadow(0 1px 0 var(--media-controls-current-shadow-color, oklch(0 0 0 / 0.25)));\n }\n}\n\n/* Seek button */\n.media-default-skin .media-button--seek {\n & .media-icon__label {\n position: absolute;\n right: -1px;\n bottom: -3px;\n font-size: 0.75em;\n font-weight: 480;\n font-variant-numeric: tabular-nums;\n }\n\n &:has(.media-icon--flipped) .media-icon__label {\n right: unset;\n left: -1px;\n }\n\n @container media-controls (width < 28rem) {\n display: none;\n }\n}\n\n/* Playback rate button */\n.media-default-skin .media-button--playback-rate {\n padding: 0;\n\n &::after {\n content: attr(data-rate) \"\\00D7\";\n width: 4ch;\n font-variant-numeric: tabular-nums;\n }\n}\n\n/* ==========================================================================\n Icons\n ========================================================================== */\n\n.media-default-skin .media-icon__container {\n position: relative;\n}\n.media-default-skin .media-icon {\n display: block;\n flex-shrink: 0;\n grid-area: 1 / 1;\n width: 18px;\n height: 18px;\n transition-behavior: allow-discrete;\n transition-property: display, opacity;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.media-default-skin .media-icon--flipped {\n scale: -1 1;\n}\n\n/* ==========================================================================\n Slider\n ========================================================================== */\n\n.media-default-skin .media-slider {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n border-radius: calc(infinity * 1px);\n outline: none;\n\n &[data-orientation=\"horizontal\"] {\n min-width: 5rem;\n width: 100%;\n height: 1.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 1.25rem;\n height: 5rem;\n }\n}\n\n/* Track */\n.media-default-skin .media-slider__track {\n position: relative;\n isolation: isolate;\n overflow: hidden;\n border-radius: inherit;\n user-select: none;\n\n &[data-orientation=\"horizontal\"] {\n width: 100%;\n height: 0.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 0.25rem;\n height: 100%;\n }\n}\n\n/* Thumb */\n.media-default-skin .media-slider__thumb {\n z-index: 10;\n position: absolute;\n transform: translate(-50%, -50%);\n width: 0.625rem;\n height: 0.625rem;\n background-color: currentColor;\n border-radius: calc(infinity * 1px);\n box-shadow:\n 0 0 0 1px var(--media-controls-current-shadow-color-subtle, oklch(0 0 0 / 0.1)),\n 0 1px 3px 0 oklch(0 0 0 / 0.15),\n 0 1px 2px -1px oklch(0 0 0 / 0.15);\n opacity: 0;\n transition-property: opacity, height, width, outline-offset;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n user-select: none;\n outline: 4px solid transparent;\n outline-offset: -4px;\n\n &[data-orientation=\"horizontal\"] {\n top: 50%;\n left: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n left: 50%;\n top: calc(100% - var(--media-slider-fill));\n }\n\n &:hover,\n &:focus {\n outline-color: oklch(from currentColor l c h / 0.25);\n outline-offset: 0;\n }\n}\n\n.media-default-skin .media-slider:active .media-slider__thumb,\n.media-default-skin .media-slider__thumb--persistent {\n width: 0.75rem;\n height: 0.75rem;\n}\n\n.media-default-skin .media-slider:hover .media-slider__thumb,\n.media-default-skin .media-slider__thumb:focus-visible,\n.media-default-skin .media-slider__thumb--persistent {\n opacity: 1;\n}\n\n/* Shared track fills */\n.media-default-skin .media-slider__buffer,\n.media-default-skin .media-slider__fill {\n position: absolute;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.media-default-skin .media-slider__buffer[data-orientation=\"horizontal\"],\n.media-default-skin .media-slider__fill[data-orientation=\"horizontal\"] {\n inset-block: 0;\n left: 0;\n}\n\n.media-default-skin .media-slider__buffer[data-orientation=\"vertical\"],\n.media-default-skin .media-slider__fill[data-orientation=\"vertical\"] {\n inset-inline: 0;\n bottom: 0;\n}\n\n/* Buffer */\n.media-default-skin .media-slider__buffer {\n background-color: oklch(from currentColor l c h / 0.2);\n transition-duration: 0.25s;\n transition-timing-function: ease-out;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-buffer);\n transition-property: width;\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-buffer);\n transition-property: height;\n }\n}\n\n/* Fill */\n.media-default-skin .media-slider__fill {\n background-color: currentColor;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-fill);\n }\n}\n\n/* Time display within slider */\n.media-default-skin .media-slider__time-display {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Popups & Tooltips\n ========================================================================== */\n\n.media-default-skin .media-popup {\n margin: 0;\n border: 0;\n color: inherit;\n overflow: visible;\n transition-property: transform, scale, opacity, filter;\n transition-duration: 200ms;\n\n &[data-starting-style],\n &[data-ending-style] {\n opacity: 0;\n transform: scale(0);\n filter: blur(8px);\n }\n\n &[data-instant] {\n transition-duration: 0ms;\n }\n\n &[data-side=\"top\"] {\n transform-origin: bottom;\n }\n &[data-side=\"bottom\"] {\n transform-origin: top;\n }\n &[data-side=\"left\"] {\n transform-origin: right;\n }\n &[data-side=\"right\"] {\n transform-origin: left;\n }\n}\n\n.media-default-skin .media-popup--popover {\n --media-popover-side-offset: 0.5rem;\n}\n\n.media-default-skin .media-popup--tooltip {\n padding: 0.25rem 0.625rem;\n border-radius: calc(infinity * 1px);\n font-size: 0.75rem;\n white-space: nowrap;\n --media-tooltip-side-offset: 0.5rem;\n}\n\n.media-default-skin .media-popup--volume {\n padding: 0.625rem 0.25rem;\n border-radius: calc(infinity * 1px);\n}\n\n/* ==========================================================================\n Captions\n ========================================================================== */\n\n.media-default-skin .media-captions {\n position: absolute;\n inset: auto 1rem 1.5rem 1rem;\n z-index: 20;\n font-size: 1rem;\n text-wrap: balance;\n pointer-events: none;\n\n @container media-root (width > 20rem) {\n font-size: 1.5rem;\n }\n\n @container media-root (width > 48rem) {\n font-size: 1.875rem;\n }\n\n @container media-root (width > 80rem) {\n font-size: 2.25rem;\n }\n}\n\n.media-default-skin .media-captions__container {\n display: flex;\n flex-direction: column;\n align-items: center;\n max-width: 42ch;\n margin: 0 auto;\n text-align: center;\n}\n\n.media-default-skin .media-captions__text {\n display: block;\n padding: 0.125rem 0.5rem;\n color: oklch(1 0 0);\n text-shadow:\n 0 0 1px oklch(0 0 0 / 0.7),\n 0 0 8px oklch(0 0 0 / 0.7);\n text-align: center;\n white-space: pre-wrap;\n line-height: 1.2;\n\n @media (prefers-contrast: more) {\n background: oklch(0 0 0 / 0.7);\n text-shadow: none;\n box-decoration-break: clone;\n }\n\n & > * {\n display: inline;\n }\n}\n\n/* Caption shifting styles (custom and native) */\n.media-default-skin {\n --media-caption-track-delay: 600ms;\n --media-caption-track-y: -0.5rem;\n\n &:has(.media-controls[data-visible]) {\n --media-caption-track-delay: 25ms;\n --media-caption-track-y: -3.5rem;\n }\n}\n\n.media-default-skin .media-captions,\n.media-default-skin video::-webkit-media-text-track-container {\n /* NOTE: The delay must account for the controls delay/duration */\n transition: transform 150ms ease-out;\n transition-delay: var(--media-caption-track-delay);\n}\n\n.media-default-skin video::-webkit-media-text-track-container {\n transform: translateY(var(--media-caption-track-y)) scale(0.98);\n z-index: 1;\n font-family: inherit;\n}\n\n/* When controls are visible, shift captions up to avoid overlap */\n.media-default-skin .media-controls[data-visible] ~ .media-captions {\n transform: translateY(calc(var(--media-caption-track-y) - 0.5rem));\n}\n\n@media (prefers-reduced-motion: reduce) {\n .media-default-skin .media-captions,\n .media-default-skin video::-webkit-media-text-track-container {\n transition-duration: 50ms;\n }\n}\n\n\n/* ==========================================================================\n Root\n ========================================================================== */\n\n.media-default-skin--video {\n background: oklch(0 0 0);\n --media-border-color: oklch(0 0 0 / 0.1);\n --media-surface-background-color: oklch(1 0 0 / 0.1);\n --media-surface-inner-border-color: oklch(1 0 0 / 0.05);\n --media-surface-outer-border-color: oklch(0 0 0 / 0.1);\n --media-surface-shadow-color: oklch(0 0 0 / 0.15);\n --media-surface-backdrop-filter: blur(64px) brightness(0.9) saturate(1.5);\n\n @media (prefers-color-scheme: dark) {\n --media-border-color: oklch(1 0 0 / 0.1);\n }\n\n /* Inner border ring */\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n z-index: 10;\n border-radius: inherit;\n box-shadow: inset 0 0 0 1px var(--media-border-color);\n pointer-events: none;\n }\n}\n\n/* ==========================================================================\n Controls (hide/show behavior)\n ========================================================================== */\n\n.media-default-skin--video .media-controls {\n position: absolute;\n bottom: 0.75rem;\n inset-inline: 0.75rem;\n z-index: 10;\n will-change: scale, transform, filter, opacity;\n transition-property: scale, transform, filter, opacity;\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n transform-origin: bottom;\n color: oklch(1 0 0);\n\n &:not([data-visible]) {\n opacity: 0;\n scale: 0.9;\n filter: blur(8px);\n transition-duration: 300ms;\n transition-delay: 500ms;\n pointer-events: none;\n\n @media (prefers-reduced-motion: reduce) {\n scale: 1;\n filter: blur(0);\n transition-duration: 100ms;\n }\n }\n}\n\n/* ==========================================================================\n Sliders\n ========================================================================== */\n\n.media-default-skin--video .media-slider__track {\n background-color: oklch(1 0 0 / 0.2);\n box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);\n}\n\n" - }, - { - "id": "default-audio", - "name": "Default Audio", - "platform": "html", - "style": "css", - "html": "\n \n\n
\n \n \n \n \n \n \n Replay\n Play\n Pause\n \n\n \n \n \n 10\n \n \n \n Seek backward 10 seconds\n \n\n \n \n \n 10\n \n \n \n Seek forward 10 seconds\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n Toggle playback rate\n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n
\n
", - "css": "/* ==========================================================================\n Icon State Visibility for Audio Skins\n\n Data-attribute-driven visibility rules for multi-state icon buttons.\n Uses :is() with both element selectors (for HTML custom element wrappers)\n and class selectors (for React rendered SVG elements).\n ========================================================================== */\n\n/* --- All icons hidden by default --- */\n\n.media-button--play .media-icon--restart,\n.media-button--play .media-icon--play,\n.media-button--play .media-icon--pause,\n.media-button--mute .media-icon--volume-off,\n.media-button--mute .media-icon--volume-low,\n.media-button--mute .media-icon--volume-high {\n display: none;\n opacity: 0;\n}\n\n/* --- Active icon per state --- */\n\n/* Play: ended → restart */\n.media-button--play[data-ended] .media-icon--restart,\n/* Play: paused (not ended) → play */\n.media-button--play:not([data-ended])[data-paused] .media-icon--play,\n/* Play: playing (not paused, not ended) → pause */\n.media-button--play:not([data-paused]):not([data-ended]) .media-icon--pause,\n/* Mute: muted → volume off */\n.media-button--mute[data-muted] .media-icon--volume-off,\n/* Mute: volume low (not muted) → volume low */\n.media-button--mute:not([data-muted])[data-volume-level=\"low\"] .media-icon--volume-low,\n/* Mute: volume high (not muted, not low) → volume high */\n.media-button--mute:not([data-muted]):not([data-volume-level=\"low\"]) .media-icon--volume-high {\n display: block;\n opacity: 1;\n}\n\n/* ==========================================================================\n Tooltip Label State Visibility\n\n Data-attribute-driven visibility rules for multi-state tooltip labels.\n Uses adjacent sibling selectors to match button state → tooltip content.\n ========================================================================== */\n\n/* --- All multi-state labels hidden by default --- */\n\n.media-tooltip-label {\n display: none;\n}\n\n/* --- Active label per state --- */\n\n/* Play: ended → replay */\n.media-button--play[data-ended] + media-tooltip .media-tooltip-label--replay,\n/* Play: paused (not ended) → play */\n .media-button--play:not([data-ended])[data-paused] + media-tooltip\n .media-tooltip-label--play,\n/* Play: playing (not paused, not ended) → pause */\n .media-button--play:not([data-paused]):not([data-ended]) + media-tooltip\n .media-tooltip-label--pause,\n/* Fullscreen: not fullscreen → enter */\n .media-button--fullscreen:not([data-fullscreen]) + media-tooltip\n .media-tooltip-label--enter-fullscreen,\n/* Fullscreen: fullscreen → exit */\n .media-button--fullscreen[data-fullscreen] + media-tooltip\n .media-tooltip-label--exit-fullscreen,\n/* Captions: not active → enable */\n .media-button--captions:not([data-active]) + media-tooltip\n .media-tooltip-label--enable-captions,\n/* Captions: active → disable */\n .media-button--captions[data-active] + media-tooltip\n .media-tooltip-label--disable-captions,\n/* PiP: not in pip → enter */\n media-pip-button:not([data-pip]) + media-tooltip\n .media-tooltip-label--enter-pip,\n/* PiP: in pip → exit */\n media-pip-button[data-pip] + media-tooltip\n .media-tooltip-label--exit-pip {\n display: block;\n}\n\n/* ==========================================================================\n Reset\n ========================================================================== */\n\n.media-default-skin *,\n.media-default-skin *::before,\n.media-default-skin *::after {\n box-sizing: border-box;\n margin: 0;\n}\n.media-default-skin img,\n.media-default-skin video,\n.media-default-skin svg {\n display: block;\n max-width: 100%;\n}\n.media-default-skin button {\n font: inherit;\n}\n@media (prefers-reduced-motion: no-preference) {\n .media-default-skin {\n interpolate-size: allow-keywords;\n }\n}\n\n/* ==========================================================================\n Root Container\n ========================================================================== */\n\n.media-default-skin {\n position: relative;\n isolation: isolate;\n display: block;\n container: media-root / inline-size;\n border-radius: var(--media-border-radius, 2rem);\n font-family:\n Inter Variable,\n Inter,\n ui-sans-serif,\n system-ui,\n sans-serif;\n font-size: 0.8125rem;\n line-height: 1.5;\n letter-spacing: normal;\n -webkit-font-smoothing: auto;\n -moz-osx-font-smoothing: auto;\n\n &:fullscreen {\n border-radius: 0;\n }\n}\n\n/* ==========================================================================\n Surface (shared glass effect for tooltips, popovers, controls)\n ========================================================================== */\n\n.media-default-skin .media-surface {\n background-color: var(--media-surface-background-color);\n backdrop-filter: var(--media-surface-backdrop-filter);\n box-shadow:\n inset 0 0 0 1px var(--media-surface-inner-border-color),\n 0 1px 3px 0 var(--media-surface-shadow-color),\n 0 1px 2px -1px var(--media-surface-shadow-color);\n\n /* Inner border ring */\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n z-index: 10;\n border-radius: inherit;\n box-shadow: 0 0 0 1px var(--media-surface-outer-border-color);\n pointer-events: none;\n }\n\n @media (prefers-reduced-transparency: reduce) {\n background-color: oklch(from var(--media-surface-background-color) l c h / 0.7);\n }\n\n @media (prefers-contrast: more) {\n background-color: oklch(from var(--media-surface-background-color) l c h / 0.9);\n }\n}\n\n/* ==========================================================================\n Buffering Indicator\n ========================================================================== */\n\n.media-default-skin .media-buffering-indicator {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n color: oklch(1 0 0);\n pointer-events: none;\n\n &[data-visible] {\n display: flex;\n }\n\n .media-surface {\n padding: 0.25rem;\n border-radius: 100%;\n }\n}\n\n/* ==========================================================================\n Error Dialog\n ========================================================================== */\n\n.media-default-skin .media-error {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.media-default-skin .media-error__dialog {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n max-width: 18rem;\n padding: 0.75rem;\n border-radius: 1.75rem;\n color: oklch(1 0 0);\n font-size: 0.875rem;\n transition-property: opacity, transform;\n transition-duration: 500ms;\n transition-delay: 100ms;\n transition-timing-function: linear(\n 0,\n 0.034 1.5%,\n 0.763 9.7%,\n 1.066 13.9%,\n 1.198 19.9%,\n 1.184 21.8%,\n 0.963 37.5%,\n 0.997 50.9%,\n 1\n );\n\n /* Simple, fast transition for reduced motion users */\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n }\n}\n\n.media-default-skin .media-error[data-starting-style] .media-error__dialog,\n.media-default-skin .media-error[data-ending-style] .media-error__dialog {\n opacity: 0;\n transform: scale(0.5);\n}\n\n.media-default-skin .media-error__content {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n padding: 0.5rem 0.5rem 0.375rem;\n}\n\n.media-default-skin .media-error__title {\n font-weight: 600;\n line-height: 1.25;\n}\n\n.media-default-skin .media-error__description {\n opacity: 0.7;\n}\n\n.media-default-skin .media-error__actions {\n display: flex;\n gap: 0.5rem;\n\n & > * {\n flex: 1;\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-default-skin .media-controls {\n container: media-controls / inline-size;\n display: flex;\n align-items: center;\n gap: 0.075rem;\n padding: 0.175rem;\n border-radius: calc(infinity * 1px);\n --media-controls-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.25));\n --media-controls-current-shadow-color-subtle: oklch(\n from var(--media-controls-current-shadow-color) l c h /\n calc(alpha * 0.4)\n );\n text-shadow: 0 0 1px var(--media-controls-current-shadow-color);\n\n @container media-root (width > 40rem) {\n gap: 0.125rem;\n padding: 0.25rem;\n }\n}\n\n/* ==========================================================================\n Time Display\n ========================================================================== */\n\n.media-default-skin .media-time {\n container: media-time / inline-size;\n display: flex;\n align-items: center;\n flex: 1;\n gap: 0.75rem;\n padding-inline: 0.5rem;\n\n & .media-time__value:first-child {\n display: none;\n\n @container media-time (width > 18rem) {\n display: block;\n }\n }\n}\n\n.media-default-skin .media-time__value {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Buttons\n ========================================================================== */\n\n/* Base button */\n.media-default-skin .media-button {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0.5rem 1rem;\n background: oklch(1 0 0);\n border: none;\n border-radius: calc(infinity * 1px);\n outline: 2px solid transparent;\n outline-offset: -2px;\n color: oklch(0 0 0);\n font-weight: 500;\n text-align: center;\n transition-property: background-color, color, outline-offset, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n cursor: pointer;\n user-select: none;\n\n &:focus-visible {\n outline-color: oklch(62.3% 0.214 259.815);\n outline-offset: 2px;\n }\n\n &[disabled] {\n opacity: 0.5;\n filter: grayscale(1);\n cursor: not-allowed;\n }\n\n &[data-availability=\"unavailable\"] {\n display: none;\n }\n}\n\n/* Icon button variant */\n.media-default-skin .media-button--icon {\n display: grid;\n width: 2.125rem;\n padding: 0;\n aspect-ratio: 1;\n background: transparent;\n color: inherit;\n text-shadow: inherit;\n\n &:hover,\n &:focus-visible,\n &[aria-expanded=\"true\"] {\n background-color: oklch(from currentColor l c h / 0.1);\n text-decoration: none;\n }\n\n &:active {\n transform: scale(0.9);\n }\n\n & .media-icon {\n filter: drop-shadow(0 1px 0 var(--media-controls-current-shadow-color, oklch(0 0 0 / 0.25)));\n }\n}\n\n/* Seek button */\n.media-default-skin .media-button--seek {\n & .media-icon__label {\n position: absolute;\n right: -1px;\n bottom: -3px;\n font-size: 0.75em;\n font-weight: 480;\n font-variant-numeric: tabular-nums;\n }\n\n &:has(.media-icon--flipped) .media-icon__label {\n right: unset;\n left: -1px;\n }\n\n @container media-controls (width < 28rem) {\n display: none;\n }\n}\n\n/* Playback rate button */\n.media-default-skin .media-button--playback-rate {\n padding: 0;\n\n &::after {\n content: attr(data-rate) \"\\00D7\";\n width: 4ch;\n font-variant-numeric: tabular-nums;\n }\n}\n\n/* ==========================================================================\n Icons\n ========================================================================== */\n\n.media-default-skin .media-icon__container {\n position: relative;\n}\n.media-default-skin .media-icon {\n display: block;\n flex-shrink: 0;\n grid-area: 1 / 1;\n width: 18px;\n height: 18px;\n transition-behavior: allow-discrete;\n transition-property: display, opacity;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.media-default-skin .media-icon--flipped {\n scale: -1 1;\n}\n\n/* ==========================================================================\n Slider\n ========================================================================== */\n\n.media-default-skin .media-slider {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n border-radius: calc(infinity * 1px);\n outline: none;\n\n &[data-orientation=\"horizontal\"] {\n min-width: 5rem;\n width: 100%;\n height: 1.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 1.25rem;\n height: 5rem;\n }\n}\n\n/* Track */\n.media-default-skin .media-slider__track {\n position: relative;\n isolation: isolate;\n overflow: hidden;\n border-radius: inherit;\n user-select: none;\n\n &[data-orientation=\"horizontal\"] {\n width: 100%;\n height: 0.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 0.25rem;\n height: 100%;\n }\n}\n\n/* Thumb */\n.media-default-skin .media-slider__thumb {\n z-index: 10;\n position: absolute;\n transform: translate(-50%, -50%);\n width: 0.625rem;\n height: 0.625rem;\n background-color: currentColor;\n border-radius: calc(infinity * 1px);\n box-shadow:\n 0 0 0 1px var(--media-controls-current-shadow-color-subtle, oklch(0 0 0 / 0.1)),\n 0 1px 3px 0 oklch(0 0 0 / 0.15),\n 0 1px 2px -1px oklch(0 0 0 / 0.15);\n opacity: 0;\n transition-property: opacity, height, width, outline-offset;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n user-select: none;\n outline: 4px solid transparent;\n outline-offset: -4px;\n\n &[data-orientation=\"horizontal\"] {\n top: 50%;\n left: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n left: 50%;\n top: calc(100% - var(--media-slider-fill));\n }\n\n &:hover,\n &:focus {\n outline-color: oklch(from currentColor l c h / 0.25);\n outline-offset: 0;\n }\n}\n\n.media-default-skin .media-slider:active .media-slider__thumb,\n.media-default-skin .media-slider__thumb--persistent {\n width: 0.75rem;\n height: 0.75rem;\n}\n\n.media-default-skin .media-slider:hover .media-slider__thumb,\n.media-default-skin .media-slider__thumb:focus-visible,\n.media-default-skin .media-slider__thumb--persistent {\n opacity: 1;\n}\n\n/* Shared track fills */\n.media-default-skin .media-slider__buffer,\n.media-default-skin .media-slider__fill {\n position: absolute;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.media-default-skin .media-slider__buffer[data-orientation=\"horizontal\"],\n.media-default-skin .media-slider__fill[data-orientation=\"horizontal\"] {\n inset-block: 0;\n left: 0;\n}\n\n.media-default-skin .media-slider__buffer[data-orientation=\"vertical\"],\n.media-default-skin .media-slider__fill[data-orientation=\"vertical\"] {\n inset-inline: 0;\n bottom: 0;\n}\n\n/* Buffer */\n.media-default-skin .media-slider__buffer {\n background-color: oklch(from currentColor l c h / 0.2);\n transition-duration: 0.25s;\n transition-timing-function: ease-out;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-buffer);\n transition-property: width;\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-buffer);\n transition-property: height;\n }\n}\n\n/* Fill */\n.media-default-skin .media-slider__fill {\n background-color: currentColor;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-fill);\n }\n}\n\n/* Time display within slider */\n.media-default-skin .media-slider__time-display {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Popups & Tooltips\n ========================================================================== */\n\n.media-default-skin .media-popup {\n margin: 0;\n border: 0;\n color: inherit;\n overflow: visible;\n transition-property: transform, scale, opacity, filter;\n transition-duration: 200ms;\n\n &[data-starting-style],\n &[data-ending-style] {\n opacity: 0;\n transform: scale(0);\n filter: blur(8px);\n }\n\n &[data-instant] {\n transition-duration: 0ms;\n }\n\n &[data-side=\"top\"] {\n transform-origin: bottom;\n }\n &[data-side=\"bottom\"] {\n transform-origin: top;\n }\n &[data-side=\"left\"] {\n transform-origin: right;\n }\n &[data-side=\"right\"] {\n transform-origin: left;\n }\n}\n\n.media-default-skin .media-popup--popover {\n --media-popover-side-offset: 0.5rem;\n}\n\n.media-default-skin .media-popup--tooltip {\n padding: 0.25rem 0.625rem;\n border-radius: calc(infinity * 1px);\n font-size: 0.75rem;\n white-space: nowrap;\n --media-tooltip-side-offset: 0.5rem;\n}\n\n.media-default-skin .media-popup--volume {\n padding: 0.625rem 0.25rem;\n border-radius: calc(infinity * 1px);\n}\n\n\n/* ==========================================================================\n Root\n ========================================================================== */\n\n.media-default-skin--audio {\n --media-border-color: oklch(0 0 0 / 0.1);\n --media-surface-background-color: oklch(1 0 0 / 0.5);\n --media-surface-inner-border-color: oklch(1 0 0 / 0.1);\n --media-surface-outer-border-color: oklch(0 0 0 / 0.05);\n --media-surface-shadow-color: oklch(0 0 0 / 0.15);\n --media-surface-backdrop-filter: blur(8px) brightness(0.98) saturate(1.2);\n\n @media (prefers-color-scheme: dark) {\n --media-border-color: oklch(1 0 0 / 0.1);\n --media-surface-background-color: oklch(0 0 0 / 0.4);\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-default-skin--audio .media-controls {\n @media (prefers-color-scheme: dark) {\n color: oklch(1 0 0);\n }\n}\n\n/* ==========================================================================\n Sliders\n ========================================================================== */\n.media-default-skin--audio .media-slider__track {\n background-color: oklch(0 0 0 / 0.1);\n\n @media (prefers-color-scheme: dark) {\n background-color: oklch(1 0 0 / 0.2);\n box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);\n }\n}\n\n" - }, - { - "id": "minimal-video", - "name": "Minimal Video", - "platform": "html", - "style": "css", - "html": "\n \n\n \n \n \n\n \n\n \n \n \n \n \n \n \n \n Replay\n Play\n Pause\n \n\n \n \n \n 10\n \n \n \n Seek backward 10 seconds\n \n\n \n \n \n 10\n \n \n \n Seek forward 10 seconds\n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n Toggle playback rate\n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n Toggle captions\n \n\n \n \n \n \n Enter picture-in-picture\n Exit picture-in-picture\n \n\n \n \n \n \n \n Enter fullscreen\n Exit fullscreen\n \n \n \n\n
\n
", - "css": "/* ==========================================================================\n Icon State Visibility for Video Skins\n\n Data-attribute-driven visibility rules for multi-state icon buttons.\n Uses :is() with both element selectors (for HTML custom element wrappers)\n and class selectors (for React rendered SVG elements).\n ========================================================================== */\n\n/* --- All icons hidden by default --- */\n\n.media-button--play .media-icon--restart,\n.media-button--play .media-icon--play,\n.media-button--play .media-icon--pause,\n.media-button--mute .media-icon--volume-off,\n.media-button--mute .media-icon--volume-low,\n.media-button--mute .media-icon--volume-high,\n.media-button--fullscreen .media-icon--fullscreen-enter,\n.media-button--fullscreen .media-icon--fullscreen-exit,\n.media-button--captions .media-icon--captions-off,\n.media-button--captions .media-icon--captions-on {\n display: none;\n opacity: 0;\n}\n\n/* --- Active icon per state --- */\n\n/* Play: ended → restart */\n.media-button--play[data-ended] .media-icon--restart,\n/* Play: paused (not ended) → play */\n.media-button--play:not([data-ended])[data-paused] .media-icon--play,\n/* Play: playing (not paused, not ended) → pause */\n.media-button--play:not([data-paused]):not([data-ended]) .media-icon--pause,\n/* Mute: muted → volume off */\n.media-button--mute[data-muted] .media-icon--volume-off,\n/* Mute: volume low (not muted) → volume low */\n.media-button--mute:not([data-muted])[data-volume-level=\"low\"] .media-icon--volume-low,\n/* Mute: volume high (not muted, not low) → volume high */\n.media-button--mute:not([data-muted]):not([data-volume-level=\"low\"]) .media-icon--volume-high,\n/* Fullscreen: not fullscreen → enter */\n.media-button--fullscreen:not([data-fullscreen]) .media-icon--fullscreen-enter,\n/* Fullscreen: fullscreen → exit */\n.media-button--fullscreen[data-fullscreen] .media-icon--fullscreen-exit,\n/* Captions: not active → captions off */\n.media-button--captions:not([data-active]) .media-icon--captions-off,\n/* Captions: active → captions on */\n.media-button--captions[data-active] .media-icon--captions-on {\n display: block;\n opacity: 1;\n}\n\n/* ==========================================================================\n Tooltip Label State Visibility\n\n Data-attribute-driven visibility rules for multi-state tooltip labels.\n Uses adjacent sibling selectors to match button state → tooltip content.\n ========================================================================== */\n\n/* --- All multi-state labels hidden by default --- */\n\n.media-tooltip-label {\n display: none;\n}\n\n/* --- Active label per state --- */\n\n/* Play: ended → replay */\n.media-button--play[data-ended] + media-tooltip .media-tooltip-label--replay,\n/* Play: paused (not ended) → play */\n .media-button--play:not([data-ended])[data-paused] + media-tooltip\n .media-tooltip-label--play,\n/* Play: playing (not paused, not ended) → pause */\n .media-button--play:not([data-paused]):not([data-ended]) + media-tooltip\n .media-tooltip-label--pause,\n/* Fullscreen: not fullscreen → enter */\n .media-button--fullscreen:not([data-fullscreen]) + media-tooltip\n .media-tooltip-label--enter-fullscreen,\n/* Fullscreen: fullscreen → exit */\n .media-button--fullscreen[data-fullscreen] + media-tooltip\n .media-tooltip-label--exit-fullscreen,\n/* Captions: not active → enable */\n .media-button--captions:not([data-active]) + media-tooltip\n .media-tooltip-label--enable-captions,\n/* Captions: active → disable */\n .media-button--captions[data-active] + media-tooltip\n .media-tooltip-label--disable-captions,\n/* PiP: not in pip → enter */\n media-pip-button:not([data-pip]) + media-tooltip\n .media-tooltip-label--enter-pip,\n/* PiP: in pip → exit */\n media-pip-button[data-pip] + media-tooltip\n .media-tooltip-label--exit-pip {\n display: block;\n}\n\n/* ==========================================================================\n Reset\n ========================================================================== */\n\n.media-minimal-skin *,\n.media-minimal-skin *::before,\n.media-minimal-skin *::after {\n box-sizing: border-box;\n margin: 0;\n}\n.media-minimal-skin img,\n.media-minimal-skin video,\n.media-minimal-skin svg {\n display: block;\n max-width: 100%;\n}\n.media-minimal-skin button {\n font: inherit;\n}\n@media (prefers-reduced-motion: no-preference) {\n .media-minimal-skin {\n interpolate-size: allow-keywords;\n }\n}\n\n/* ==========================================================================\n Root Container\n ========================================================================== */\n\n.media-minimal-skin {\n position: relative;\n isolation: isolate;\n display: block;\n container: media-root / inline-size;\n border-radius: var(--media-border-radius, 0.75rem);\n font-family:\n Inter Variable,\n Inter,\n ui-sans-serif,\n system-ui,\n sans-serif;\n font-size: 0.8125rem;\n line-height: 1.5;\n letter-spacing: normal;\n -webkit-font-smoothing: auto;\n -moz-osx-font-smoothing: auto;\n}\n\n/* ==========================================================================\n Media Element\n ========================================================================== */\n\n.media-minimal-skin ::slotted(video),\n.media-minimal-skin video {\n display: block;\n width: 100%;\n height: 100%;\n border-radius: var(--media-border-radius, 0.75rem);\n}\n\n/* ==========================================================================\n Poster Image\n ========================================================================== */\n\n.media-minimal-skin > img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n border-radius: inherit;\n object-fit: cover;\n transition: opacity 0.25s;\n pointer-events: none;\n\n &:not([data-visible]) {\n opacity: 0;\n }\n}\n\n/* ==========================================================================\n Overlay / Scrim\n ========================================================================== */\n\n.media-minimal-skin .media-overlay {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background-image: linear-gradient(to top, oklch(0 0 0 / 0.7), oklch(0 0 0 / 0.5) 7.5rem, oklch(0 0 0 / 0));\n backdrop-filter: blur(0) saturate(1.2) brightness(0.9);\n opacity: 0;\n transition-property: opacity, backdrop-filter;\n transition-duration: 500ms;\n transition-delay: 500ms;\n transition-timing-function: ease-out;\n pointer-events: none;\n\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n }\n}\n\n.media-minimal-skin .media-controls[data-visible] ~ .media-overlay,\n.media-minimal-skin .media-error[data-open] ~ .media-overlay {\n opacity: 1;\n transition-duration: 150ms;\n transition-delay: 0ms;\n}\n\n.media-minimal-skin .media-error[data-open] ~ .media-overlay {\n backdrop-filter: blur(8px) saturate(1.2) brightness(0.9);\n}\n\n/* ==========================================================================\n Buffering Indicator\n ========================================================================== */\n\n.media-minimal-skin .media-buffering-indicator {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n color: oklch(1 0 0);\n pointer-events: none;\n\n &[data-visible] {\n display: flex;\n }\n}\n\n/* ==========================================================================\n Error Dialog\n ========================================================================== */\n\n.media-minimal-skin .media-error {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n\n.media-minimal-skin .media-error__dialog {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n max-width: 16rem;\n padding: 1rem;\n color: oklch(1 0 0);\n font-size: 0.875rem;\n text-shadow: 0 1px 0 oklch(0 0 0 / 0.5);\n transition-property: opacity, transform;\n transition-duration: 500ms;\n transition-delay: 100ms;\n transition-timing-function: linear(\n 0,\n 0.034 1.5%,\n 0.763 9.7%,\n 1.066 13.9%,\n 1.198 19.9%,\n 1.184 21.8%,\n 0.963 37.5%,\n 0.997 50.9%,\n 1\n );\n\n /* Simple, fast transition for reduced motion users */\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n }\n}\n\n.media-minimal-skin .media-error[data-starting-style] .media-error__dialog,\n.media-minimal-skin .media-error[data-ending-style] .media-error__dialog {\n opacity: 0;\n transform: scale(0.5);\n}\n\n.media-minimal-skin .media-error__content {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n padding: 0.375rem 0;\n}\n\n.media-minimal-skin .media-error__title {\n font-weight: 600;\n line-height: 1.25;\n}\n\n.media-minimal-skin .media-error__description {\n opacity: 0.7;\n}\n\n.media-minimal-skin .media-error__actions {\n display: flex;\n gap: 0.5rem;\n\n & > * {\n flex: 1;\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-minimal-skin .media-controls {\n container: media-controls / inline-size;\n display: flex;\n align-items: center;\n --media-controls-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.25));\n --media-controls-current-shadow-color-subtle: oklch(\n from var(--media-controls-current-shadow-color) l c h /\n calc(alpha * 0.4)\n );\n text-shadow: 0 0 1px var(--media-controls-current-shadow-color);\n}\n\n/* ==========================================================================\n Time Controls & Display\n ========================================================================== */\n\n.media-minimal-skin .media-time-controls {\n display: flex;\n flex-direction: row-reverse;\n align-items: center;\n flex: 1;\n gap: 0.75rem;\n}\n.media-minimal-skin .media-time {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n.media-minimal-skin .media-time__value {\n font-variant-numeric: tabular-nums;\n}\n.media-minimal-skin .media-time__value--current,\n.media-minimal-skin .media-time__separator {\n display: none;\n}\n@container media-controls (width > 28rem) {\n .media-minimal-skin .media-time-controls {\n flex-direction: row;\n }\n .media-minimal-skin .media-time__value--duration,\n .media-minimal-skin .media-time__separator {\n color: oklch(from currentColor l c h / 0.6);\n }\n .media-minimal-skin .media-time__value--current,\n .media-minimal-skin .media-time__separator {\n display: inline;\n }\n}\n\n/* ==========================================================================\n Button Groups\n ========================================================================== */\n\n.media-minimal-skin .media-button-group {\n display: flex;\n align-items: center;\n gap: 0.075rem;\n\n @container media-root (width > 40rem) {\n gap: 0.125rem;\n }\n}\n\n/* ==========================================================================\n Buttons\n ========================================================================== */\n\n/* Base button */\n.media-minimal-skin .media-button {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0.5rem 1rem;\n background: oklch(1 0 0);\n border: none;\n border-radius: 0.5rem;\n outline: 2px solid transparent;\n outline-offset: -2px;\n color: oklch(0 0 0);\n font-weight: 500;\n text-align: center;\n text-shadow: inherit;\n transition-property: background-color, color, outline-offset, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n cursor: pointer;\n user-select: none;\n\n &:focus-visible {\n outline-color: currentColor;\n outline-offset: 2px;\n }\n\n &[disabled] {\n opacity: 0.5;\n filter: grayscale(1);\n cursor: not-allowed;\n }\n\n &[data-availability=\"unavailable\"] {\n display: none;\n }\n}\n\n/* Icon button variant */\n.media-minimal-skin .media-button--icon {\n display: grid;\n width: 2.375rem;\n padding: 0;\n aspect-ratio: 1;\n background: transparent;\n color: inherit;\n\n &:hover,\n &:focus-visible,\n &[aria-expanded=\"true\"] {\n color: oklch(from currentColor l c h / 0.8);\n text-decoration: none;\n }\n\n &:active {\n transform: scale(0.9);\n }\n\n & .media-icon {\n filter: drop-shadow(0 1px 0 var(--media-controls-current-shadow-color, oklch(0 0 0 / 0.25)));\n }\n}\n\n/* Seek button */\n.media-minimal-skin .media-button--seek {\n & .media-icon__label {\n position: absolute;\n right: -1px;\n bottom: -3px;\n font-size: 0.75em;\n font-weight: 480;\n font-variant-numeric: tabular-nums;\n }\n\n &:has(.media-icon--flipped) .media-icon__label {\n right: unset;\n left: -1px;\n }\n\n @container media-controls (width < 28rem) {\n display: none;\n }\n}\n\n/* Playback rate button */\n.media-minimal-skin .media-button--playback-rate {\n padding: 0;\n\n &::after {\n content: attr(data-rate) \"\\00D7\";\n width: 4ch;\n font-variant-numeric: tabular-nums;\n }\n}\n\n/* ==========================================================================\n Icons\n ========================================================================== */\n\n.media-minimal-skin .media-icon__container {\n position: relative;\n}\n.media-minimal-skin .media-icon {\n display: block;\n flex-shrink: 0;\n grid-area: 1 / 1;\n width: 18px;\n height: 18px;\n transition-behavior: allow-discrete;\n transition-property: display, opacity;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.media-minimal-skin .media-icon--flipped {\n scale: -1 1;\n}\n\n/* ==========================================================================\n Slider\n ========================================================================== */\n\n.media-minimal-skin .media-slider {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n border-radius: calc(infinity * 1px);\n outline: none;\n\n &[data-orientation=\"horizontal\"] {\n min-width: 5rem;\n width: 100%;\n height: 1.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 1.25rem;\n height: 4.5rem;\n }\n}\n\n/* Track */\n.media-minimal-skin .media-slider__track {\n position: relative;\n isolation: isolate;\n overflow: hidden;\n border-radius: inherit;\n user-select: none;\n background-color: oklch(from currentColor l c h / 0.2);\n\n &[data-orientation=\"horizontal\"] {\n width: 100%;\n height: 0.1875rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 0.1875rem;\n height: 100%;\n }\n}\n\n/* Thumb */\n.media-minimal-skin .media-slider__thumb {\n position: absolute;\n transform: translate(-50%, -50%);\n z-index: 10;\n width: 0.75rem;\n height: 0.75rem;\n background-color: currentColor;\n border-radius: calc(infinity * 1px);\n box-shadow:\n 0 0 0 1px var(--media-controls-current-shadow-color-subtle, oklch(0 0 0 / 0.1)),\n 0 1px 3px 0 oklch(0 0 0 / 0.15),\n 0 1px 2px -1px oklch(0 0 0 / 0.15);\n opacity: 0;\n scale: 0.7;\n transform-origin: center;\n transition-property: opacity, scale, outline-offset;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n user-select: none;\n outline: 2px solid transparent;\n outline-offset: -2px;\n\n &[data-orientation=\"horizontal\"] {\n top: 50%;\n left: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n left: 50%;\n top: calc(100% - var(--media-slider-fill));\n }\n\n &:focus-visible {\n outline-color: currentColor;\n outline-offset: 2px;\n }\n}\n\n.media-minimal-skin .media-slider:hover .media-slider__thumb,\n.media-minimal-skin .media-slider:focus-within .media-slider__thumb,\n.media-minimal-skin .media-slider__thumb--persistent {\n opacity: 1;\n scale: 1;\n}\n\n/* Shared track fills */\n.media-minimal-skin .media-slider__buffer,\n.media-minimal-skin .media-slider__fill {\n position: absolute;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.media-minimal-skin .media-slider__buffer[data-orientation=\"horizontal\"],\n.media-minimal-skin .media-slider__fill[data-orientation=\"horizontal\"] {\n inset-block: 0;\n left: 0;\n}\n\n.media-minimal-skin .media-slider__buffer[data-orientation=\"vertical\"],\n.media-minimal-skin .media-slider__fill[data-orientation=\"vertical\"] {\n inset-inline: 0;\n bottom: 0;\n}\n\n/* Buffer */\n.media-minimal-skin .media-slider__buffer {\n background-color: oklch(from currentColor l c h / 0.2);\n transition-duration: 0.25s;\n transition-timing-function: ease-out;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-buffer);\n transition-property: width;\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-buffer);\n transition-property: height;\n }\n}\n\n/* Fill */\n.media-minimal-skin .media-slider__fill {\n background-color: currentColor;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-fill);\n }\n}\n\n/* Time display within slider */\n.media-minimal-skin .media-slider__time-display {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Popups & Animations\n ========================================================================== */\n\n.media-minimal-skin .media-popup {\n margin: 0;\n border: 0;\n color: inherit;\n overflow: visible;\n transition-property: transform, scale, opacity, filter;\n transition-duration: 200ms;\n\n &[data-starting-style],\n &[data-ending-style] {\n opacity: 0;\n transform: scale(0);\n filter: blur(8px);\n }\n\n &[data-instant] {\n transition-duration: 0ms;\n }\n\n &[data-side=\"top\"] {\n transform-origin: bottom;\n }\n &[data-side=\"bottom\"] {\n transform-origin: top;\n }\n &[data-side=\"left\"] {\n transform-origin: right;\n }\n &[data-side=\"right\"] {\n transform-origin: left;\n }\n}\n\n.media-minimal-skin .media-popup--tooltip {\n padding: 0.25rem 0.5rem;\n border-radius: 0.25rem;\n background-color: oklch(1 0 0 / 0.1);\n backdrop-filter: blur(64px) brightness(0.9) saturate(1.5);\n box-shadow:\n 0 4px 6px -1px oklch(0 0 0 / 0.1),\n 0 2px 4px -2px oklch(0 0 0 / 0.1);\n font-size: 0.75rem;\n white-space: nowrap;\n --media-tooltip-side-offset: 0.5rem;\n\n @media (prefers-reduced-transparency: reduce) {\n background-color: oklch(0 0 0 / 0.7);\n }\n\n @media (prefers-contrast: more) {\n background-color: oklch(0 0 0 / 0.9);\n }\n}\n\n/* ==========================================================================\n Captions\n ========================================================================== */\n\n.media-minimal-skin .media-captions {\n position: absolute;\n inset: auto 1rem 1.5rem 1rem;\n z-index: 20;\n font-size: 1rem;\n text-wrap: balance;\n pointer-events: none;\n\n @container media-root (width > 20rem) {\n font-size: 1.5rem;\n }\n\n @container media-root (width > 48rem) {\n font-size: 1.875rem;\n }\n\n @container media-root (width > 80rem) {\n font-size: 2.25rem;\n }\n}\n\n.media-minimal-skin .media-captions__container {\n display: flex;\n flex-direction: column;\n align-items: center;\n max-width: 42ch;\n margin: 0 auto;\n text-align: center;\n}\n\n.media-minimal-skin .media-captions__text {\n display: block;\n padding: 0.125rem 0.5rem;\n color: oklch(1 0 0);\n text-shadow:\n 0 0 1px oklch(0 0 0 / 0.7),\n 0 0 8px oklch(0 0 0 / 0.7);\n text-align: center;\n white-space: pre-wrap;\n line-height: 1.2;\n\n @media (prefers-contrast: more) {\n background: oklch(0 0 0 / 0.7);\n text-shadow: none;\n box-decoration-break: clone;\n }\n\n & > * {\n display: inline;\n }\n}\n\n/* Caption shifting styles (custom and native) */\n.media-minimal-skin {\n --media-caption-track-delay: 600ms;\n --media-caption-track-y: -0.5rem;\n\n &:has(.media-controls[data-visible]) {\n --media-caption-track-delay: 25ms;\n --media-caption-track-y: -3rem;\n }\n}\n\n.media-minimal-skin .media-captions,\n.media-minimal-skin video::-webkit-media-text-track-container {\n /* NOTE: The delay must account for the controls delay/duration */\n transition: transform 150ms ease-out;\n transition-delay: var(--media-caption-track-delay);\n}\n\n.media-minimal-skin video::-webkit-media-text-track-container {\n transform: translateY(var(--media-caption-track-y)) scale(0.98);\n z-index: 1;\n font-family: inherit;\n}\n\n/* When controls are visible, shift captions up to avoid overlap */\n.media-minimal-skin .media-controls[data-visible] ~ .media-captions {\n transform: translateY(calc(var(--media-caption-track-y) - 0.5rem));\n}\n\n@media (prefers-reduced-motion: reduce) {\n .media-minimal-skin .media-captions,\n .media-minimal-skin video::-webkit-media-text-track-container {\n transition-duration: 50ms;\n }\n}\n\n\n/* ==========================================================================\n Root\n ========================================================================== */\n\n.media-minimal-skin--video {\n background: oklch(0 0 0);\n\n /* Border ring */\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n z-index: 10;\n border-radius: inherit;\n box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.15);\n pointer-events: none;\n\n @media (prefers-color-scheme: dark) {\n box-shadow: inset 0 0 0 1px oklch(1 0 0 / 0.15);\n }\n }\n\n /* Fullscreen */\n &:fullscreen {\n border-radius: 0;\n }\n}\n\n/* ==========================================================================\n Controls (hide/show behavior)\n ========================================================================== */\n\n.media-minimal-skin--video .media-controls {\n position: absolute;\n bottom: 0;\n inset-inline: 0;\n z-index: 10;\n gap: 0.5rem;\n padding: 2rem 0.375rem 0.375rem 0.375rem;\n will-change: transform, filter, opacity;\n transition-property: transform, filter, opacity;\n transition-duration: 75ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n color: oklch(1 0 0);\n\n @container media-root (width > 40rem) {\n gap: 0.875rem;\n padding: 2.5rem 0.75rem 0.75rem 0.75rem;\n }\n\n &:not([data-visible]) {\n opacity: 0;\n transform: translateY(100%);\n filter: blur(8px);\n transition-duration: 500ms;\n transition-delay: 500ms;\n pointer-events: none;\n\n @media (prefers-reduced-motion: reduce) {\n scale: 1;\n transform: translateY(0);\n filter: blur(0);\n transition-duration: 100ms;\n }\n }\n}\n\n/* ==========================================================================\n Sliders\n ========================================================================== */\n\n.media-minimal-skin--video .media-slider__track {\n box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);\n}\n\n/* ==========================================================================\n Popups & Animations\n ========================================================================== */\n\n.media-minimal-skin--video .media-popup--volume {\n --media-popover-side-offset: 0.5rem;\n background: transparent;\n padding: 0.25rem;\n}\n\n" - }, - { - "id": "minimal-audio", - "name": "Minimal Audio", - "platform": "html", - "style": "css", - "html": "\n \n\n
\n \n \n \n \n \n \n \n Replay\n Play\n Pause\n \n\n \n \n \n 10\n \n \n \n Seek backward 10 seconds\n \n\n \n \n \n 10\n \n \n \n Seek forward 10 seconds\n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n Toggle playback rate\n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n
\n
", - "css": "/* ==========================================================================\n Icon State Visibility for Audio Skins\n\n Data-attribute-driven visibility rules for multi-state icon buttons.\n Uses :is() with both element selectors (for HTML custom element wrappers)\n and class selectors (for React rendered SVG elements).\n ========================================================================== */\n\n/* --- All icons hidden by default --- */\n\n.media-button--play .media-icon--restart,\n.media-button--play .media-icon--play,\n.media-button--play .media-icon--pause,\n.media-button--mute .media-icon--volume-off,\n.media-button--mute .media-icon--volume-low,\n.media-button--mute .media-icon--volume-high {\n display: none;\n opacity: 0;\n}\n\n/* --- Active icon per state --- */\n\n/* Play: ended → restart */\n.media-button--play[data-ended] .media-icon--restart,\n/* Play: paused (not ended) → play */\n.media-button--play:not([data-ended])[data-paused] .media-icon--play,\n/* Play: playing (not paused, not ended) → pause */\n.media-button--play:not([data-paused]):not([data-ended]) .media-icon--pause,\n/* Mute: muted → volume off */\n.media-button--mute[data-muted] .media-icon--volume-off,\n/* Mute: volume low (not muted) → volume low */\n.media-button--mute:not([data-muted])[data-volume-level=\"low\"] .media-icon--volume-low,\n/* Mute: volume high (not muted, not low) → volume high */\n.media-button--mute:not([data-muted]):not([data-volume-level=\"low\"]) .media-icon--volume-high {\n display: block;\n opacity: 1;\n}\n\n/* ==========================================================================\n Tooltip Label State Visibility\n\n Data-attribute-driven visibility rules for multi-state tooltip labels.\n Uses adjacent sibling selectors to match button state → tooltip content.\n ========================================================================== */\n\n/* --- All multi-state labels hidden by default --- */\n\n.media-tooltip-label {\n display: none;\n}\n\n/* --- Active label per state --- */\n\n/* Play: ended → replay */\n.media-button--play[data-ended] + media-tooltip .media-tooltip-label--replay,\n/* Play: paused (not ended) → play */\n .media-button--play:not([data-ended])[data-paused] + media-tooltip\n .media-tooltip-label--play,\n/* Play: playing (not paused, not ended) → pause */\n .media-button--play:not([data-paused]):not([data-ended]) + media-tooltip\n .media-tooltip-label--pause,\n/* Fullscreen: not fullscreen → enter */\n .media-button--fullscreen:not([data-fullscreen]) + media-tooltip\n .media-tooltip-label--enter-fullscreen,\n/* Fullscreen: fullscreen → exit */\n .media-button--fullscreen[data-fullscreen] + media-tooltip\n .media-tooltip-label--exit-fullscreen,\n/* Captions: not active → enable */\n .media-button--captions:not([data-active]) + media-tooltip\n .media-tooltip-label--enable-captions,\n/* Captions: active → disable */\n .media-button--captions[data-active] + media-tooltip\n .media-tooltip-label--disable-captions,\n/* PiP: not in pip → enter */\n media-pip-button:not([data-pip]) + media-tooltip\n .media-tooltip-label--enter-pip,\n/* PiP: in pip → exit */\n media-pip-button[data-pip] + media-tooltip\n .media-tooltip-label--exit-pip {\n display: block;\n}\n\n/* ==========================================================================\n Reset\n ========================================================================== */\n\n.media-minimal-skin *,\n.media-minimal-skin *::before,\n.media-minimal-skin *::after {\n box-sizing: border-box;\n margin: 0;\n}\n.media-minimal-skin img,\n.media-minimal-skin video,\n.media-minimal-skin svg {\n display: block;\n max-width: 100%;\n}\n.media-minimal-skin button {\n font: inherit;\n}\n@media (prefers-reduced-motion: no-preference) {\n .media-minimal-skin {\n interpolate-size: allow-keywords;\n }\n}\n\n/* ==========================================================================\n Root Container\n ========================================================================== */\n\n.media-minimal-skin {\n position: relative;\n isolation: isolate;\n display: block;\n container: media-root / inline-size;\n border-radius: var(--media-border-radius, 0.75rem);\n font-family:\n Inter Variable,\n Inter,\n ui-sans-serif,\n system-ui,\n sans-serif;\n font-size: 0.8125rem;\n line-height: 1.5;\n letter-spacing: normal;\n -webkit-font-smoothing: auto;\n -moz-osx-font-smoothing: auto;\n}\n\n/* ==========================================================================\n Buffering Indicator\n ========================================================================== */\n\n.media-minimal-skin .media-buffering-indicator {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n color: oklch(1 0 0);\n pointer-events: none;\n\n &[data-visible] {\n display: flex;\n }\n}\n\n/* ==========================================================================\n Error Dialog\n ========================================================================== */\n\n.media-minimal-skin .media-error {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n\n.media-minimal-skin .media-error__dialog {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n max-width: 16rem;\n padding: 1rem;\n color: oklch(1 0 0);\n font-size: 0.875rem;\n text-shadow: 0 1px 0 oklch(0 0 0 / 0.5);\n transition-property: opacity, transform;\n transition-duration: 500ms;\n transition-delay: 100ms;\n transition-timing-function: linear(\n 0,\n 0.034 1.5%,\n 0.763 9.7%,\n 1.066 13.9%,\n 1.198 19.9%,\n 1.184 21.8%,\n 0.963 37.5%,\n 0.997 50.9%,\n 1\n );\n\n /* Simple, fast transition for reduced motion users */\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n }\n}\n\n.media-minimal-skin .media-error[data-starting-style] .media-error__dialog,\n.media-minimal-skin .media-error[data-ending-style] .media-error__dialog {\n opacity: 0;\n transform: scale(0.5);\n}\n\n.media-minimal-skin .media-error__content {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n padding: 0.375rem 0;\n}\n\n.media-minimal-skin .media-error__title {\n font-weight: 600;\n line-height: 1.25;\n}\n\n.media-minimal-skin .media-error__description {\n opacity: 0.7;\n}\n\n.media-minimal-skin .media-error__actions {\n display: flex;\n gap: 0.5rem;\n\n & > * {\n flex: 1;\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-minimal-skin .media-controls {\n container: media-controls / inline-size;\n display: flex;\n align-items: center;\n --media-controls-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.25));\n --media-controls-current-shadow-color-subtle: oklch(\n from var(--media-controls-current-shadow-color) l c h /\n calc(alpha * 0.4)\n );\n text-shadow: 0 0 1px var(--media-controls-current-shadow-color);\n}\n\n/* ==========================================================================\n Time Controls & Display\n ========================================================================== */\n\n.media-minimal-skin .media-time-controls {\n display: flex;\n flex-direction: row-reverse;\n align-items: center;\n flex: 1;\n gap: 0.75rem;\n}\n.media-minimal-skin .media-time {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n.media-minimal-skin .media-time__value {\n font-variant-numeric: tabular-nums;\n}\n.media-minimal-skin .media-time__value--current,\n.media-minimal-skin .media-time__separator {\n display: none;\n}\n@container media-controls (width > 28rem) {\n .media-minimal-skin .media-time-controls {\n flex-direction: row;\n }\n .media-minimal-skin .media-time__value--duration,\n .media-minimal-skin .media-time__separator {\n color: oklch(from currentColor l c h / 0.6);\n }\n .media-minimal-skin .media-time__value--current,\n .media-minimal-skin .media-time__separator {\n display: inline;\n }\n}\n\n/* ==========================================================================\n Button Groups\n ========================================================================== */\n\n.media-minimal-skin .media-button-group {\n display: flex;\n align-items: center;\n gap: 0.075rem;\n\n @container media-root (width > 40rem) {\n gap: 0.125rem;\n }\n}\n\n/* ==========================================================================\n Buttons\n ========================================================================== */\n\n/* Base button */\n.media-minimal-skin .media-button {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0.5rem 1rem;\n background: oklch(1 0 0);\n border: none;\n border-radius: 0.5rem;\n outline: 2px solid transparent;\n outline-offset: -2px;\n color: oklch(0 0 0);\n font-weight: 500;\n text-align: center;\n text-shadow: inherit;\n transition-property: background-color, color, outline-offset, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n cursor: pointer;\n user-select: none;\n\n &:focus-visible {\n outline-color: currentColor;\n outline-offset: 2px;\n }\n\n &[disabled] {\n opacity: 0.5;\n filter: grayscale(1);\n cursor: not-allowed;\n }\n\n &[data-availability=\"unavailable\"] {\n display: none;\n }\n}\n\n/* Icon button variant */\n.media-minimal-skin .media-button--icon {\n display: grid;\n width: 2.375rem;\n padding: 0;\n aspect-ratio: 1;\n background: transparent;\n color: inherit;\n\n &:hover,\n &:focus-visible,\n &[aria-expanded=\"true\"] {\n color: oklch(from currentColor l c h / 0.8);\n text-decoration: none;\n }\n\n &:active {\n transform: scale(0.9);\n }\n\n & .media-icon {\n filter: drop-shadow(0 1px 0 var(--media-controls-current-shadow-color, oklch(0 0 0 / 0.25)));\n }\n}\n\n/* Seek button */\n.media-minimal-skin .media-button--seek {\n & .media-icon__label {\n position: absolute;\n right: -1px;\n bottom: -3px;\n font-size: 0.75em;\n font-weight: 480;\n font-variant-numeric: tabular-nums;\n }\n\n &:has(.media-icon--flipped) .media-icon__label {\n right: unset;\n left: -1px;\n }\n\n @container media-controls (width < 28rem) {\n display: none;\n }\n}\n\n/* Playback rate button */\n.media-minimal-skin .media-button--playback-rate {\n padding: 0;\n\n &::after {\n content: attr(data-rate) \"\\00D7\";\n width: 4ch;\n font-variant-numeric: tabular-nums;\n }\n}\n\n/* ==========================================================================\n Icons\n ========================================================================== */\n\n.media-minimal-skin .media-icon__container {\n position: relative;\n}\n.media-minimal-skin .media-icon {\n display: block;\n flex-shrink: 0;\n grid-area: 1 / 1;\n width: 18px;\n height: 18px;\n transition-behavior: allow-discrete;\n transition-property: display, opacity;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.media-minimal-skin .media-icon--flipped {\n scale: -1 1;\n}\n\n/* ==========================================================================\n Slider\n ========================================================================== */\n\n.media-minimal-skin .media-slider {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n border-radius: calc(infinity * 1px);\n outline: none;\n\n &[data-orientation=\"horizontal\"] {\n min-width: 5rem;\n width: 100%;\n height: 1.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 1.25rem;\n height: 4.5rem;\n }\n}\n\n/* Track */\n.media-minimal-skin .media-slider__track {\n position: relative;\n isolation: isolate;\n overflow: hidden;\n border-radius: inherit;\n user-select: none;\n background-color: oklch(from currentColor l c h / 0.2);\n\n &[data-orientation=\"horizontal\"] {\n width: 100%;\n height: 0.1875rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 0.1875rem;\n height: 100%;\n }\n}\n\n/* Thumb */\n.media-minimal-skin .media-slider__thumb {\n position: absolute;\n transform: translate(-50%, -50%);\n z-index: 10;\n width: 0.75rem;\n height: 0.75rem;\n background-color: currentColor;\n border-radius: calc(infinity * 1px);\n box-shadow:\n 0 0 0 1px var(--media-controls-current-shadow-color-subtle, oklch(0 0 0 / 0.1)),\n 0 1px 3px 0 oklch(0 0 0 / 0.15),\n 0 1px 2px -1px oklch(0 0 0 / 0.15);\n opacity: 0;\n scale: 0.7;\n transform-origin: center;\n transition-property: opacity, scale, outline-offset;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n user-select: none;\n outline: 2px solid transparent;\n outline-offset: -2px;\n\n &[data-orientation=\"horizontal\"] {\n top: 50%;\n left: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n left: 50%;\n top: calc(100% - var(--media-slider-fill));\n }\n\n &:focus-visible {\n outline-color: currentColor;\n outline-offset: 2px;\n }\n}\n\n.media-minimal-skin .media-slider:hover .media-slider__thumb,\n.media-minimal-skin .media-slider:focus-within .media-slider__thumb,\n.media-minimal-skin .media-slider__thumb--persistent {\n opacity: 1;\n scale: 1;\n}\n\n/* Shared track fills */\n.media-minimal-skin .media-slider__buffer,\n.media-minimal-skin .media-slider__fill {\n position: absolute;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.media-minimal-skin .media-slider__buffer[data-orientation=\"horizontal\"],\n.media-minimal-skin .media-slider__fill[data-orientation=\"horizontal\"] {\n inset-block: 0;\n left: 0;\n}\n\n.media-minimal-skin .media-slider__buffer[data-orientation=\"vertical\"],\n.media-minimal-skin .media-slider__fill[data-orientation=\"vertical\"] {\n inset-inline: 0;\n bottom: 0;\n}\n\n/* Buffer */\n.media-minimal-skin .media-slider__buffer {\n background-color: oklch(from currentColor l c h / 0.2);\n transition-duration: 0.25s;\n transition-timing-function: ease-out;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-buffer);\n transition-property: width;\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-buffer);\n transition-property: height;\n }\n}\n\n/* Fill */\n.media-minimal-skin .media-slider__fill {\n background-color: currentColor;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-fill);\n }\n}\n\n/* Time display within slider */\n.media-minimal-skin .media-slider__time-display {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Popups & Animations\n ========================================================================== */\n\n.media-minimal-skin .media-popup {\n margin: 0;\n border: 0;\n color: inherit;\n overflow: visible;\n transition-property: transform, scale, opacity, filter;\n transition-duration: 200ms;\n\n &[data-starting-style],\n &[data-ending-style] {\n opacity: 0;\n transform: scale(0);\n filter: blur(8px);\n }\n\n &[data-instant] {\n transition-duration: 0ms;\n }\n\n &[data-side=\"top\"] {\n transform-origin: bottom;\n }\n &[data-side=\"bottom\"] {\n transform-origin: top;\n }\n &[data-side=\"left\"] {\n transform-origin: right;\n }\n &[data-side=\"right\"] {\n transform-origin: left;\n }\n}\n\n.media-minimal-skin .media-popup--tooltip {\n padding: 0.25rem 0.5rem;\n border-radius: 0.25rem;\n background-color: oklch(1 0 0 / 0.1);\n backdrop-filter: blur(64px) brightness(0.9) saturate(1.5);\n box-shadow:\n 0 4px 6px -1px oklch(0 0 0 / 0.1),\n 0 2px 4px -2px oklch(0 0 0 / 0.1);\n font-size: 0.75rem;\n white-space: nowrap;\n --media-tooltip-side-offset: 0.5rem;\n\n @media (prefers-reduced-transparency: reduce) {\n background-color: oklch(0 0 0 / 0.7);\n }\n\n @media (prefers-contrast: more) {\n background-color: oklch(0 0 0 / 0.9);\n }\n}\n\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-minimal-skin--audio {\n --media-controls-background-color: oklch(1 0 0);\n --media-controls-border-color: oklch(0 0 0 / 0.1);\n --media-controls-text-color: oklch(0 0 0);\n\n @media (prefers-color-scheme: dark) {\n --media-controls-background-color: oklch(0 0 0);\n --media-controls-border-color: oklch(1 0 0 / 0.1);\n --media-controls-text-color: oklch(1 0 0);\n }\n}\n.media-minimal-skin--audio .media-controls {\n gap: 0.5rem;\n padding: 0.375rem;\n background-color: var(--media-controls-background-color);\n backdrop-filter: blur(8px) brightness(0.98) saturate(1.2);\n border-radius: var(--media-border-radius, 0.75rem);\n color: var(--media-controls-text-color);\n box-shadow: 0 0 0 1px var(--media-controls-border-color);\n}\n\n/* ==========================================================================\n Popups & Animations\n ========================================================================== */\n\n.media-minimal-skin--audio .media-popup--volume {\n background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%);\n padding: 0.5rem 0 0.5rem 4rem;\n --media-popover-side-offset: 0.75rem;\n}\n\n" - }, - { - "id": "default-video-tailwind", - "name": "Default Video (Tailwind)", - "platform": "html", - "style": "tailwind", - "html": "img]:absolute [&>img]:inset-0 [&>img]:w-full [&>img]:h-full [&>img]:rounded-[inherit] [&>img]:object-cover [&>img]:pointer-events-none [&>img]:transition-opacity [&>img]:duration-250 [&>img:not([data-visible])]:opacity-0 [--media-caption-track-delay:600ms] [--media-caption-track-y:-0.5rem] has-[[data-controls][data-visible]]:[--media-caption-track-delay:25ms] has-[[data-controls][data-visible]]:[--media-caption-track-y:-3.5rem] [&:fullscreen]:rounded-none\">\n \n\n \n\n \n\n \n \n \n \n \n \n \n \n Replay\n Play\n Pause\n \n \n\n \n \n \n 10\n \n \n \n Seek backward 10 seconds\n \n\n \n \n \n 10\n \n \n \n Seek forward 10 seconds\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n Toggle playback rate\n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n Enable captions\n Disable captions\n \n \n\n \n \n \n \n \n Enter picture-in-picture\n Exit picture-in-picture\n \n \n\n \n \n \n \n \n \n Enter fullscreen\n Exit fullscreen\n \n \n \n\n
\n
" - }, - { - "id": "default-audio-tailwind", - "name": "Default Audio (Tailwind)", - "platform": "html", - "style": "tailwind", - "html": "\n \n\n
\n \n \n \n \n \n \n \n Replay\n Play\n Pause\n \n \n\n \n \n \n 10\n \n \n \n Seek backward 10 seconds\n \n\n \n \n \n 10\n \n \n \n Seek forward 10 seconds\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n Toggle playback rate\n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n
\n
" - }, - { - "id": "minimal-video-tailwind", - "name": "Minimal Video (Tailwind)", - "platform": "html", - "style": "tailwind", - "html": "img]:absolute [&>img]:inset-0 [&>img]:w-full [&>img]:h-full [&>img]:rounded-[inherit] [&>img]:object-cover [&>img]:pointer-events-none [&>img]:transition-opacity [&>img]:duration-250 [&>img:not([data-visible])]:opacity-0 [--media-caption-track-delay:600ms] [--media-caption-track-y:-0.5rem] has-[[data-controls][data-visible]]:[--media-caption-track-delay:25ms] has-[[data-controls][data-visible]]:[--media-caption-track-y:-3.5rem] [&:fullscreen]:rounded-none\">\n \n\n \n\n \n\n \n \n \n \n \n \n \n \n \n Replay\n Play\n Pause\n \n \n\n \n \n \n 10\n \n \n \n Seek backward 10 seconds\n \n\n \n \n \n 10\n \n \n \n Seek forward 10 seconds\n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n Toggle playback rate\n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n Enable captions\n Disable captions\n \n \n\n \n \n \n \n \n Enter picture-in-picture\n Exit picture-in-picture\n \n \n\n \n \n \n \n \n \n Enter fullscreen\n Exit fullscreen\n \n \n \n \n\n
\n
" - }, - { - "id": "minimal-audio-tailwind", - "name": "Minimal Audio (Tailwind)", - "platform": "html", - "style": "tailwind", - "html": "\n \n\n
\n \n \n \n \n \n \n \n \n Replay\n Play\n Pause\n \n \n\n \n \n \n 10\n \n \n \n Seek backward 10 seconds\n \n\n \n \n \n 10\n \n \n \n Seek forward 10 seconds\n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n Toggle playback rate\n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n
\n
" - }, - { - "id": "default-video-react", - "name": "Default Video (React)", - "platform": "react", - "style": "css", - "tsx": "import { selectError } from '@videojs/core/dom';\nimport { type ComponentProps, forwardRef, type ReactNode, useRef, type PropsWithChildren, type CSSProperties } from 'react';\n\nimport { AlertDialog, Container, usePlayer, BufferingIndicator, CaptionsButton, Controls, FullscreenButton, MuteButton, PiPButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\n\nexport interface ErrorDialogClasses {\n root?: string;\n dialog?: string;\n content?: string;\n title?: string;\n description?: string;\n actions?: string;\n close?: string;\n}\n\nexport function ErrorDialog({ classes }: { classes?: ErrorDialogClasses }): ReactNode {\n const errorState = usePlayer(selectError);\n const lastError = useRef(errorState?.error);\n if (errorState?.error) lastError.current = errorState.error;\n\n if (!errorState) return null;\n\n return (\n {\n if (!open) errorState.dismissError();\n }}\n >\n \n
\n
\n Something went wrong.\n \n {lastError.current?.message ?? 'An error occurred while trying to play the video. Please try again.'}\n \n
\n
\n OK\n
\n
\n
\n \n );\n}\n\nconst SEEK_TIME = 10;\n\nexport type VideoSkinProps = PropsWithChildren<{ style?: CSSProperties; className?: string }>;\n\nconst Button = forwardRef>(function Button({ className, ...props }, ref) {\n return \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n Seek backward {SEEK_TIME} seconds\n \n \n\n \n (\n \n )}\n />\n }\n />\n \n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n )}\n />\n }\n />\n \n \n \n \n \n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n \n\n {/*
\n
\n An example cue\n \n

Another example cue with HTML

\n
\n
\n
*/}\n\n
\n \n );\n}\n", - "jsx": "import { selectError } from '@videojs/core/dom';\nimport { forwardRef, useRef } from 'react';\nimport { AlertDialog, Container, usePlayer, BufferingIndicator, CaptionsButton, Controls, FullscreenButton, MuteButton, PiPButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nexport function ErrorDialog({ classes }) {\n const errorState = usePlayer(selectError);\n const lastError = useRef(errorState?.error);\n if (errorState?.error)\n lastError.current = errorState.error;\n if (!errorState)\n return null;\n return ( {\n if (!open)\n errorState.dismissError();\n }}>\n \n
\n
\n Something went wrong.\n \n {lastError.current?.message ?? 'An error occurred while trying to play the video. Please try again.'}\n \n
\n
\n OK\n
\n
\n
\n
);\n}\nconst SEEK_TIME = 10;\nconst Button = forwardRef(function Button({ className, ...props }, ref) {\n return )}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n Seek backward {SEEK_TIME} seconds\n \n \n\n \n ()}/>}/>\n \n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n )}/>}/>\n \n \n \n \n \n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n \n\n {/*
\n
\n An example cue\n \n

Another example cue with HTML

\n
\n
\n
*/}\n\n
\n );\n}\n", - "css": "/* ==========================================================================\n Icon State Visibility for Video Skins\n\n Data-attribute-driven visibility rules for multi-state icon buttons.\n Uses :is() with both element selectors (for HTML custom element wrappers)\n and class selectors (for React rendered SVG elements).\n ========================================================================== */\n\n/* --- All icons hidden by default --- */\n\n.media-button--play .media-icon--restart,\n.media-button--play .media-icon--play,\n.media-button--play .media-icon--pause,\n.media-button--mute .media-icon--volume-off,\n.media-button--mute .media-icon--volume-low,\n.media-button--mute .media-icon--volume-high,\n.media-button--fullscreen .media-icon--fullscreen-enter,\n.media-button--fullscreen .media-icon--fullscreen-exit,\n.media-button--captions .media-icon--captions-off,\n.media-button--captions .media-icon--captions-on {\n display: none;\n opacity: 0;\n}\n\n/* --- Active icon per state --- */\n\n/* Play: ended → restart */\n.media-button--play[data-ended] .media-icon--restart,\n/* Play: paused (not ended) → play */\n.media-button--play:not([data-ended])[data-paused] .media-icon--play,\n/* Play: playing (not paused, not ended) → pause */\n.media-button--play:not([data-paused]):not([data-ended]) .media-icon--pause,\n/* Mute: muted → volume off */\n.media-button--mute[data-muted] .media-icon--volume-off,\n/* Mute: volume low (not muted) → volume low */\n.media-button--mute:not([data-muted])[data-volume-level=\"low\"] .media-icon--volume-low,\n/* Mute: volume high (not muted, not low) → volume high */\n.media-button--mute:not([data-muted]):not([data-volume-level=\"low\"]) .media-icon--volume-high,\n/* Fullscreen: not fullscreen → enter */\n.media-button--fullscreen:not([data-fullscreen]) .media-icon--fullscreen-enter,\n/* Fullscreen: fullscreen → exit */\n.media-button--fullscreen[data-fullscreen] .media-icon--fullscreen-exit,\n/* Captions: not active → captions off */\n.media-button--captions:not([data-active]) .media-icon--captions-off,\n/* Captions: active → captions on */\n.media-button--captions[data-active] .media-icon--captions-on {\n display: block;\n opacity: 1;\n}\n\n/* ==========================================================================\n Tooltip Label State Visibility\n\n Data-attribute-driven visibility rules for multi-state tooltip labels.\n Uses adjacent sibling selectors to match button state → tooltip content.\n ========================================================================== */\n\n/* --- All multi-state labels hidden by default --- */\n\n.media-tooltip-label {\n display: none;\n}\n\n/* --- Active label per state --- */\n\n/* Play: ended → replay */\n.media-button--play[data-ended] + media-tooltip .media-tooltip-label--replay,\n/* Play: paused (not ended) → play */\n .media-button--play:not([data-ended])[data-paused] + media-tooltip\n .media-tooltip-label--play,\n/* Play: playing (not paused, not ended) → pause */\n .media-button--play:not([data-paused]):not([data-ended]) + media-tooltip\n .media-tooltip-label--pause,\n/* Fullscreen: not fullscreen → enter */\n .media-button--fullscreen:not([data-fullscreen]) + media-tooltip\n .media-tooltip-label--enter-fullscreen,\n/* Fullscreen: fullscreen → exit */\n .media-button--fullscreen[data-fullscreen] + media-tooltip\n .media-tooltip-label--exit-fullscreen,\n/* Captions: not active → enable */\n .media-button--captions:not([data-active]) + media-tooltip\n .media-tooltip-label--enable-captions,\n/* Captions: active → disable */\n .media-button--captions[data-active] + media-tooltip\n .media-tooltip-label--disable-captions,\n/* PiP: not in pip → enter */\n media-pip-button:not([data-pip]) + media-tooltip\n .media-tooltip-label--enter-pip,\n/* PiP: in pip → exit */\n media-pip-button[data-pip] + media-tooltip\n .media-tooltip-label--exit-pip {\n display: block;\n}\n\n/* ==========================================================================\n Reset\n ========================================================================== */\n\n.media-default-skin *,\n.media-default-skin *::before,\n.media-default-skin *::after {\n box-sizing: border-box;\n margin: 0;\n}\n.media-default-skin img,\n.media-default-skin video,\n.media-default-skin svg {\n display: block;\n max-width: 100%;\n}\n.media-default-skin button {\n font: inherit;\n}\n@media (prefers-reduced-motion: no-preference) {\n .media-default-skin {\n interpolate-size: allow-keywords;\n }\n}\n\n/* ==========================================================================\n Root Container\n ========================================================================== */\n\n.media-default-skin {\n position: relative;\n isolation: isolate;\n display: block;\n container: media-root / inline-size;\n border-radius: var(--media-border-radius, 2rem);\n font-family:\n Inter Variable,\n Inter,\n ui-sans-serif,\n system-ui,\n sans-serif;\n font-size: 0.8125rem;\n line-height: 1.5;\n letter-spacing: normal;\n -webkit-font-smoothing: auto;\n -moz-osx-font-smoothing: auto;\n\n &:fullscreen {\n border-radius: 0;\n }\n}\n\n/* ==========================================================================\n Surface (shared glass effect for tooltips, popovers, controls)\n ========================================================================== */\n\n.media-default-skin .media-surface {\n background-color: var(--media-surface-background-color);\n backdrop-filter: var(--media-surface-backdrop-filter);\n box-shadow:\n inset 0 0 0 1px var(--media-surface-inner-border-color),\n 0 1px 3px 0 var(--media-surface-shadow-color),\n 0 1px 2px -1px var(--media-surface-shadow-color);\n\n /* Inner border ring */\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n z-index: 10;\n border-radius: inherit;\n box-shadow: 0 0 0 1px var(--media-surface-outer-border-color);\n pointer-events: none;\n }\n\n @media (prefers-reduced-transparency: reduce) {\n background-color: oklch(from var(--media-surface-background-color) l c h / 0.7);\n }\n\n @media (prefers-contrast: more) {\n background-color: oklch(from var(--media-surface-background-color) l c h / 0.9);\n }\n}\n\n/* ==========================================================================\n Media Element\n ========================================================================== */\n\n.media-default-skin ::slotted(video),\n.media-default-skin video {\n display: block;\n width: 100%;\n height: 100%;\n border-radius: var(--media-border-radius, 2rem);\n}\n\n/* ==========================================================================\n Poster Image\n ========================================================================== */\n\n.media-default-skin > img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n border-radius: inherit;\n object-fit: cover;\n transition: opacity 0.25s;\n pointer-events: none;\n\n &:not([data-visible]) {\n opacity: 0;\n }\n}\n\n/* ==========================================================================\n Overlay / Scrim\n ========================================================================== */\n\n.media-default-skin .media-overlay {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background-image: linear-gradient(to top, oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.3), oklch(0 0 0 / 0));\n backdrop-filter: blur(0) saturate(1.2) brightness(0.9);\n opacity: 0;\n transition-property: opacity, backdrop-filter;\n transition-duration: 300ms;\n transition-delay: 500ms;\n transition-timing-function: ease-out;\n pointer-events: none;\n\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n }\n}\n\n.media-default-skin .media-controls[data-visible] ~ .media-overlay,\n.media-default-skin .media-error[data-open] ~ .media-overlay {\n opacity: 1;\n transition-duration: 150ms;\n transition-delay: 0ms;\n}\n\n.media-default-skin .media-error[data-open] ~ .media-overlay {\n backdrop-filter: blur(8px) saturate(1.2) brightness(0.9);\n}\n\n/* ==========================================================================\n Buffering Indicator\n ========================================================================== */\n\n.media-default-skin .media-buffering-indicator {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n color: oklch(1 0 0);\n pointer-events: none;\n\n &[data-visible] {\n display: flex;\n }\n\n .media-surface {\n padding: 0.25rem;\n border-radius: 100%;\n }\n}\n\n/* ==========================================================================\n Error Dialog\n ========================================================================== */\n\n.media-default-skin .media-error {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.media-default-skin .media-error__dialog {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n max-width: 18rem;\n padding: 0.75rem;\n border-radius: 1.75rem;\n color: oklch(1 0 0);\n font-size: 0.875rem;\n transition-property: opacity, transform;\n transition-duration: 500ms;\n transition-delay: 100ms;\n transition-timing-function: linear(\n 0,\n 0.034 1.5%,\n 0.763 9.7%,\n 1.066 13.9%,\n 1.198 19.9%,\n 1.184 21.8%,\n 0.963 37.5%,\n 0.997 50.9%,\n 1\n );\n\n /* Simple, fast transition for reduced motion users */\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n }\n}\n\n.media-default-skin .media-error[data-starting-style] .media-error__dialog,\n.media-default-skin .media-error[data-ending-style] .media-error__dialog {\n opacity: 0;\n transform: scale(0.5);\n}\n\n.media-default-skin .media-error__content {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n padding: 0.5rem 0.5rem 0.375rem;\n}\n\n.media-default-skin .media-error__title {\n font-weight: 600;\n line-height: 1.25;\n}\n\n.media-default-skin .media-error__description {\n opacity: 0.7;\n}\n\n.media-default-skin .media-error__actions {\n display: flex;\n gap: 0.5rem;\n\n & > * {\n flex: 1;\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-default-skin .media-controls {\n container: media-controls / inline-size;\n display: flex;\n align-items: center;\n gap: 0.075rem;\n padding: 0.175rem;\n border-radius: calc(infinity * 1px);\n --media-controls-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.25));\n --media-controls-current-shadow-color-subtle: oklch(\n from var(--media-controls-current-shadow-color) l c h /\n calc(alpha * 0.4)\n );\n text-shadow: 0 0 1px var(--media-controls-current-shadow-color);\n\n @container media-root (width > 40rem) {\n gap: 0.125rem;\n padding: 0.25rem;\n }\n}\n\n/* ==========================================================================\n Time Display\n ========================================================================== */\n\n.media-default-skin .media-time {\n container: media-time / inline-size;\n display: flex;\n align-items: center;\n flex: 1;\n gap: 0.75rem;\n padding-inline: 0.5rem;\n\n & .media-time__value:first-child {\n display: none;\n\n @container media-time (width > 18rem) {\n display: block;\n }\n }\n}\n\n.media-default-skin .media-time__value {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Buttons\n ========================================================================== */\n\n/* Base button */\n.media-default-skin .media-button {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0.5rem 1rem;\n background: oklch(1 0 0);\n border: none;\n border-radius: calc(infinity * 1px);\n outline: 2px solid transparent;\n outline-offset: -2px;\n color: oklch(0 0 0);\n font-weight: 500;\n text-align: center;\n transition-property: background-color, color, outline-offset, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n cursor: pointer;\n user-select: none;\n\n &:focus-visible {\n outline-color: oklch(62.3% 0.214 259.815);\n outline-offset: 2px;\n }\n\n &[disabled] {\n opacity: 0.5;\n filter: grayscale(1);\n cursor: not-allowed;\n }\n\n &[data-availability=\"unavailable\"] {\n display: none;\n }\n}\n\n/* Icon button variant */\n.media-default-skin .media-button--icon {\n display: grid;\n width: 2.125rem;\n padding: 0;\n aspect-ratio: 1;\n background: transparent;\n color: inherit;\n text-shadow: inherit;\n\n &:hover,\n &:focus-visible,\n &[aria-expanded=\"true\"] {\n background-color: oklch(from currentColor l c h / 0.1);\n text-decoration: none;\n }\n\n &:active {\n transform: scale(0.9);\n }\n\n & .media-icon {\n filter: drop-shadow(0 1px 0 var(--media-controls-current-shadow-color, oklch(0 0 0 / 0.25)));\n }\n}\n\n/* Seek button */\n.media-default-skin .media-button--seek {\n & .media-icon__label {\n position: absolute;\n right: -1px;\n bottom: -3px;\n font-size: 0.75em;\n font-weight: 480;\n font-variant-numeric: tabular-nums;\n }\n\n &:has(.media-icon--flipped) .media-icon__label {\n right: unset;\n left: -1px;\n }\n\n @container media-controls (width < 28rem) {\n display: none;\n }\n}\n\n/* Playback rate button */\n.media-default-skin .media-button--playback-rate {\n padding: 0;\n\n &::after {\n content: attr(data-rate) \"\\00D7\";\n width: 4ch;\n font-variant-numeric: tabular-nums;\n }\n}\n\n/* ==========================================================================\n Icons\n ========================================================================== */\n\n.media-default-skin .media-icon__container {\n position: relative;\n}\n.media-default-skin .media-icon {\n display: block;\n flex-shrink: 0;\n grid-area: 1 / 1;\n width: 18px;\n height: 18px;\n transition-behavior: allow-discrete;\n transition-property: display, opacity;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.media-default-skin .media-icon--flipped {\n scale: -1 1;\n}\n\n/* ==========================================================================\n Slider\n ========================================================================== */\n\n.media-default-skin .media-slider {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n border-radius: calc(infinity * 1px);\n outline: none;\n\n &[data-orientation=\"horizontal\"] {\n min-width: 5rem;\n width: 100%;\n height: 1.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 1.25rem;\n height: 5rem;\n }\n}\n\n/* Track */\n.media-default-skin .media-slider__track {\n position: relative;\n isolation: isolate;\n overflow: hidden;\n border-radius: inherit;\n user-select: none;\n\n &[data-orientation=\"horizontal\"] {\n width: 100%;\n height: 0.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 0.25rem;\n height: 100%;\n }\n}\n\n/* Thumb */\n.media-default-skin .media-slider__thumb {\n z-index: 10;\n position: absolute;\n transform: translate(-50%, -50%);\n width: 0.625rem;\n height: 0.625rem;\n background-color: currentColor;\n border-radius: calc(infinity * 1px);\n box-shadow:\n 0 0 0 1px var(--media-controls-current-shadow-color-subtle, oklch(0 0 0 / 0.1)),\n 0 1px 3px 0 oklch(0 0 0 / 0.15),\n 0 1px 2px -1px oklch(0 0 0 / 0.15);\n opacity: 0;\n transition-property: opacity, height, width, outline-offset;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n user-select: none;\n outline: 4px solid transparent;\n outline-offset: -4px;\n\n &[data-orientation=\"horizontal\"] {\n top: 50%;\n left: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n left: 50%;\n top: calc(100% - var(--media-slider-fill));\n }\n\n &:hover,\n &:focus {\n outline-color: oklch(from currentColor l c h / 0.25);\n outline-offset: 0;\n }\n}\n\n.media-default-skin .media-slider:active .media-slider__thumb,\n.media-default-skin .media-slider__thumb--persistent {\n width: 0.75rem;\n height: 0.75rem;\n}\n\n.media-default-skin .media-slider:hover .media-slider__thumb,\n.media-default-skin .media-slider__thumb:focus-visible,\n.media-default-skin .media-slider__thumb--persistent {\n opacity: 1;\n}\n\n/* Shared track fills */\n.media-default-skin .media-slider__buffer,\n.media-default-skin .media-slider__fill {\n position: absolute;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.media-default-skin .media-slider__buffer[data-orientation=\"horizontal\"],\n.media-default-skin .media-slider__fill[data-orientation=\"horizontal\"] {\n inset-block: 0;\n left: 0;\n}\n\n.media-default-skin .media-slider__buffer[data-orientation=\"vertical\"],\n.media-default-skin .media-slider__fill[data-orientation=\"vertical\"] {\n inset-inline: 0;\n bottom: 0;\n}\n\n/* Buffer */\n.media-default-skin .media-slider__buffer {\n background-color: oklch(from currentColor l c h / 0.2);\n transition-duration: 0.25s;\n transition-timing-function: ease-out;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-buffer);\n transition-property: width;\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-buffer);\n transition-property: height;\n }\n}\n\n/* Fill */\n.media-default-skin .media-slider__fill {\n background-color: currentColor;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-fill);\n }\n}\n\n/* Time display within slider */\n.media-default-skin .media-slider__time-display {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Popups & Tooltips\n ========================================================================== */\n\n.media-default-skin .media-popup {\n margin: 0;\n border: 0;\n color: inherit;\n overflow: visible;\n transition-property: transform, scale, opacity, filter;\n transition-duration: 200ms;\n\n &[data-starting-style],\n &[data-ending-style] {\n opacity: 0;\n transform: scale(0);\n filter: blur(8px);\n }\n\n &[data-instant] {\n transition-duration: 0ms;\n }\n\n &[data-side=\"top\"] {\n transform-origin: bottom;\n }\n &[data-side=\"bottom\"] {\n transform-origin: top;\n }\n &[data-side=\"left\"] {\n transform-origin: right;\n }\n &[data-side=\"right\"] {\n transform-origin: left;\n }\n}\n\n.media-default-skin .media-popup--popover {\n --media-popover-side-offset: 0.5rem;\n}\n\n.media-default-skin .media-popup--tooltip {\n padding: 0.25rem 0.625rem;\n border-radius: calc(infinity * 1px);\n font-size: 0.75rem;\n white-space: nowrap;\n --media-tooltip-side-offset: 0.5rem;\n}\n\n.media-default-skin .media-popup--volume {\n padding: 0.625rem 0.25rem;\n border-radius: calc(infinity * 1px);\n}\n\n/* ==========================================================================\n Captions\n ========================================================================== */\n\n.media-default-skin .media-captions {\n position: absolute;\n inset: auto 1rem 1.5rem 1rem;\n z-index: 20;\n font-size: 1rem;\n text-wrap: balance;\n pointer-events: none;\n\n @container media-root (width > 20rem) {\n font-size: 1.5rem;\n }\n\n @container media-root (width > 48rem) {\n font-size: 1.875rem;\n }\n\n @container media-root (width > 80rem) {\n font-size: 2.25rem;\n }\n}\n\n.media-default-skin .media-captions__container {\n display: flex;\n flex-direction: column;\n align-items: center;\n max-width: 42ch;\n margin: 0 auto;\n text-align: center;\n}\n\n.media-default-skin .media-captions__text {\n display: block;\n padding: 0.125rem 0.5rem;\n color: oklch(1 0 0);\n text-shadow:\n 0 0 1px oklch(0 0 0 / 0.7),\n 0 0 8px oklch(0 0 0 / 0.7);\n text-align: center;\n white-space: pre-wrap;\n line-height: 1.2;\n\n @media (prefers-contrast: more) {\n background: oklch(0 0 0 / 0.7);\n text-shadow: none;\n box-decoration-break: clone;\n }\n\n & > * {\n display: inline;\n }\n}\n\n/* Caption shifting styles (custom and native) */\n.media-default-skin {\n --media-caption-track-delay: 600ms;\n --media-caption-track-y: -0.5rem;\n\n &:has(.media-controls[data-visible]) {\n --media-caption-track-delay: 25ms;\n --media-caption-track-y: -3.5rem;\n }\n}\n\n.media-default-skin .media-captions,\n.media-default-skin video::-webkit-media-text-track-container {\n /* NOTE: The delay must account for the controls delay/duration */\n transition: transform 150ms ease-out;\n transition-delay: var(--media-caption-track-delay);\n}\n\n.media-default-skin video::-webkit-media-text-track-container {\n transform: translateY(var(--media-caption-track-y)) scale(0.98);\n z-index: 1;\n font-family: inherit;\n}\n\n/* When controls are visible, shift captions up to avoid overlap */\n.media-default-skin .media-controls[data-visible] ~ .media-captions {\n transform: translateY(calc(var(--media-caption-track-y) - 0.5rem));\n}\n\n@media (prefers-reduced-motion: reduce) {\n .media-default-skin .media-captions,\n .media-default-skin video::-webkit-media-text-track-container {\n transition-duration: 50ms;\n }\n}\n\n\n/* ==========================================================================\n Root\n ========================================================================== */\n\n.media-default-skin--video {\n background: oklch(0 0 0);\n --media-border-color: oklch(0 0 0 / 0.1);\n --media-surface-background-color: oklch(1 0 0 / 0.1);\n --media-surface-inner-border-color: oklch(1 0 0 / 0.05);\n --media-surface-outer-border-color: oklch(0 0 0 / 0.1);\n --media-surface-shadow-color: oklch(0 0 0 / 0.15);\n --media-surface-backdrop-filter: blur(64px) brightness(0.9) saturate(1.5);\n\n @media (prefers-color-scheme: dark) {\n --media-border-color: oklch(1 0 0 / 0.1);\n }\n\n /* Inner border ring */\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n z-index: 10;\n border-radius: inherit;\n box-shadow: inset 0 0 0 1px var(--media-border-color);\n pointer-events: none;\n }\n}\n\n/* ==========================================================================\n Controls (hide/show behavior)\n ========================================================================== */\n\n.media-default-skin--video .media-controls {\n position: absolute;\n bottom: 0.75rem;\n inset-inline: 0.75rem;\n z-index: 10;\n will-change: scale, transform, filter, opacity;\n transition-property: scale, transform, filter, opacity;\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n transform-origin: bottom;\n color: oklch(1 0 0);\n\n &:not([data-visible]) {\n opacity: 0;\n scale: 0.9;\n filter: blur(8px);\n transition-duration: 300ms;\n transition-delay: 500ms;\n pointer-events: none;\n\n @media (prefers-reduced-motion: reduce) {\n scale: 1;\n filter: blur(0);\n transition-duration: 100ms;\n }\n }\n}\n\n/* ==========================================================================\n Sliders\n ========================================================================== */\n\n.media-default-skin--video .media-slider__track {\n background-color: oklch(1 0 0 / 0.2);\n box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);\n}\n\n" - }, - { - "id": "default-audio-react", - "name": "Default Audio (React)", - "platform": "react", - "style": "css", - "tsx": "import { type ComponentProps, forwardRef, type ReactNode, type PropsWithChildren, type CSSProperties } from 'react';\n\nimport { Container, usePlayer, MuteButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nconst SEEK_TIME = 10;\n\nexport type AudioSkinProps = PropsWithChildren<{ style?: CSSProperties; className?: string }>;\n\nconst Button = forwardRef>(function Button({ className, ...props }, ref) {\n return \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n Seek backward {SEEK_TIME} seconds\n \n \n\n \n (\n \n )}\n />\n }\n />\n \n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n )}\n />\n }\n />\n \n \n \n \n \n \n \n \n \n
\n \n );\n}\n", - "jsx": "import { forwardRef } from 'react';\nimport { Container, usePlayer, MuteButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nconst SEEK_TIME = 10;\nconst Button = forwardRef(function Button({ className, ...props }, ref) {\n return )}/>}/>\n \n \n \n
\n\n \n ()}/>}/>\n \n Seek backward {SEEK_TIME} seconds\n \n \n\n \n ()}/>}/>\n \n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n )}/>}/>\n \n \n \n \n \n \n \n \n \n
\n );\n}\n", - "css": "/* ==========================================================================\n Icon State Visibility for Audio Skins\n\n Data-attribute-driven visibility rules for multi-state icon buttons.\n Uses :is() with both element selectors (for HTML custom element wrappers)\n and class selectors (for React rendered SVG elements).\n ========================================================================== */\n\n/* --- All icons hidden by default --- */\n\n.media-button--play .media-icon--restart,\n.media-button--play .media-icon--play,\n.media-button--play .media-icon--pause,\n.media-button--mute .media-icon--volume-off,\n.media-button--mute .media-icon--volume-low,\n.media-button--mute .media-icon--volume-high {\n display: none;\n opacity: 0;\n}\n\n/* --- Active icon per state --- */\n\n/* Play: ended → restart */\n.media-button--play[data-ended] .media-icon--restart,\n/* Play: paused (not ended) → play */\n.media-button--play:not([data-ended])[data-paused] .media-icon--play,\n/* Play: playing (not paused, not ended) → pause */\n.media-button--play:not([data-paused]):not([data-ended]) .media-icon--pause,\n/* Mute: muted → volume off */\n.media-button--mute[data-muted] .media-icon--volume-off,\n/* Mute: volume low (not muted) → volume low */\n.media-button--mute:not([data-muted])[data-volume-level=\"low\"] .media-icon--volume-low,\n/* Mute: volume high (not muted, not low) → volume high */\n.media-button--mute:not([data-muted]):not([data-volume-level=\"low\"]) .media-icon--volume-high {\n display: block;\n opacity: 1;\n}\n\n/* ==========================================================================\n Tooltip Label State Visibility\n\n Data-attribute-driven visibility rules for multi-state tooltip labels.\n Uses adjacent sibling selectors to match button state → tooltip content.\n ========================================================================== */\n\n/* --- All multi-state labels hidden by default --- */\n\n.media-tooltip-label {\n display: none;\n}\n\n/* --- Active label per state --- */\n\n/* Play: ended → replay */\n.media-button--play[data-ended] + media-tooltip .media-tooltip-label--replay,\n/* Play: paused (not ended) → play */\n .media-button--play:not([data-ended])[data-paused] + media-tooltip\n .media-tooltip-label--play,\n/* Play: playing (not paused, not ended) → pause */\n .media-button--play:not([data-paused]):not([data-ended]) + media-tooltip\n .media-tooltip-label--pause,\n/* Fullscreen: not fullscreen → enter */\n .media-button--fullscreen:not([data-fullscreen]) + media-tooltip\n .media-tooltip-label--enter-fullscreen,\n/* Fullscreen: fullscreen → exit */\n .media-button--fullscreen[data-fullscreen] + media-tooltip\n .media-tooltip-label--exit-fullscreen,\n/* Captions: not active → enable */\n .media-button--captions:not([data-active]) + media-tooltip\n .media-tooltip-label--enable-captions,\n/* Captions: active → disable */\n .media-button--captions[data-active] + media-tooltip\n .media-tooltip-label--disable-captions,\n/* PiP: not in pip → enter */\n media-pip-button:not([data-pip]) + media-tooltip\n .media-tooltip-label--enter-pip,\n/* PiP: in pip → exit */\n media-pip-button[data-pip] + media-tooltip\n .media-tooltip-label--exit-pip {\n display: block;\n}\n\n/* ==========================================================================\n Reset\n ========================================================================== */\n\n.media-default-skin *,\n.media-default-skin *::before,\n.media-default-skin *::after {\n box-sizing: border-box;\n margin: 0;\n}\n.media-default-skin img,\n.media-default-skin video,\n.media-default-skin svg {\n display: block;\n max-width: 100%;\n}\n.media-default-skin button {\n font: inherit;\n}\n@media (prefers-reduced-motion: no-preference) {\n .media-default-skin {\n interpolate-size: allow-keywords;\n }\n}\n\n/* ==========================================================================\n Root Container\n ========================================================================== */\n\n.media-default-skin {\n position: relative;\n isolation: isolate;\n display: block;\n container: media-root / inline-size;\n border-radius: var(--media-border-radius, 2rem);\n font-family:\n Inter Variable,\n Inter,\n ui-sans-serif,\n system-ui,\n sans-serif;\n font-size: 0.8125rem;\n line-height: 1.5;\n letter-spacing: normal;\n -webkit-font-smoothing: auto;\n -moz-osx-font-smoothing: auto;\n\n &:fullscreen {\n border-radius: 0;\n }\n}\n\n/* ==========================================================================\n Surface (shared glass effect for tooltips, popovers, controls)\n ========================================================================== */\n\n.media-default-skin .media-surface {\n background-color: var(--media-surface-background-color);\n backdrop-filter: var(--media-surface-backdrop-filter);\n box-shadow:\n inset 0 0 0 1px var(--media-surface-inner-border-color),\n 0 1px 3px 0 var(--media-surface-shadow-color),\n 0 1px 2px -1px var(--media-surface-shadow-color);\n\n /* Inner border ring */\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n z-index: 10;\n border-radius: inherit;\n box-shadow: 0 0 0 1px var(--media-surface-outer-border-color);\n pointer-events: none;\n }\n\n @media (prefers-reduced-transparency: reduce) {\n background-color: oklch(from var(--media-surface-background-color) l c h / 0.7);\n }\n\n @media (prefers-contrast: more) {\n background-color: oklch(from var(--media-surface-background-color) l c h / 0.9);\n }\n}\n\n/* ==========================================================================\n Buffering Indicator\n ========================================================================== */\n\n.media-default-skin .media-buffering-indicator {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n color: oklch(1 0 0);\n pointer-events: none;\n\n &[data-visible] {\n display: flex;\n }\n\n .media-surface {\n padding: 0.25rem;\n border-radius: 100%;\n }\n}\n\n/* ==========================================================================\n Error Dialog\n ========================================================================== */\n\n.media-default-skin .media-error {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.media-default-skin .media-error__dialog {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n max-width: 18rem;\n padding: 0.75rem;\n border-radius: 1.75rem;\n color: oklch(1 0 0);\n font-size: 0.875rem;\n transition-property: opacity, transform;\n transition-duration: 500ms;\n transition-delay: 100ms;\n transition-timing-function: linear(\n 0,\n 0.034 1.5%,\n 0.763 9.7%,\n 1.066 13.9%,\n 1.198 19.9%,\n 1.184 21.8%,\n 0.963 37.5%,\n 0.997 50.9%,\n 1\n );\n\n /* Simple, fast transition for reduced motion users */\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n }\n}\n\n.media-default-skin .media-error[data-starting-style] .media-error__dialog,\n.media-default-skin .media-error[data-ending-style] .media-error__dialog {\n opacity: 0;\n transform: scale(0.5);\n}\n\n.media-default-skin .media-error__content {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n padding: 0.5rem 0.5rem 0.375rem;\n}\n\n.media-default-skin .media-error__title {\n font-weight: 600;\n line-height: 1.25;\n}\n\n.media-default-skin .media-error__description {\n opacity: 0.7;\n}\n\n.media-default-skin .media-error__actions {\n display: flex;\n gap: 0.5rem;\n\n & > * {\n flex: 1;\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-default-skin .media-controls {\n container: media-controls / inline-size;\n display: flex;\n align-items: center;\n gap: 0.075rem;\n padding: 0.175rem;\n border-radius: calc(infinity * 1px);\n --media-controls-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.25));\n --media-controls-current-shadow-color-subtle: oklch(\n from var(--media-controls-current-shadow-color) l c h /\n calc(alpha * 0.4)\n );\n text-shadow: 0 0 1px var(--media-controls-current-shadow-color);\n\n @container media-root (width > 40rem) {\n gap: 0.125rem;\n padding: 0.25rem;\n }\n}\n\n/* ==========================================================================\n Time Display\n ========================================================================== */\n\n.media-default-skin .media-time {\n container: media-time / inline-size;\n display: flex;\n align-items: center;\n flex: 1;\n gap: 0.75rem;\n padding-inline: 0.5rem;\n\n & .media-time__value:first-child {\n display: none;\n\n @container media-time (width > 18rem) {\n display: block;\n }\n }\n}\n\n.media-default-skin .media-time__value {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Buttons\n ========================================================================== */\n\n/* Base button */\n.media-default-skin .media-button {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0.5rem 1rem;\n background: oklch(1 0 0);\n border: none;\n border-radius: calc(infinity * 1px);\n outline: 2px solid transparent;\n outline-offset: -2px;\n color: oklch(0 0 0);\n font-weight: 500;\n text-align: center;\n transition-property: background-color, color, outline-offset, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n cursor: pointer;\n user-select: none;\n\n &:focus-visible {\n outline-color: oklch(62.3% 0.214 259.815);\n outline-offset: 2px;\n }\n\n &[disabled] {\n opacity: 0.5;\n filter: grayscale(1);\n cursor: not-allowed;\n }\n\n &[data-availability=\"unavailable\"] {\n display: none;\n }\n}\n\n/* Icon button variant */\n.media-default-skin .media-button--icon {\n display: grid;\n width: 2.125rem;\n padding: 0;\n aspect-ratio: 1;\n background: transparent;\n color: inherit;\n text-shadow: inherit;\n\n &:hover,\n &:focus-visible,\n &[aria-expanded=\"true\"] {\n background-color: oklch(from currentColor l c h / 0.1);\n text-decoration: none;\n }\n\n &:active {\n transform: scale(0.9);\n }\n\n & .media-icon {\n filter: drop-shadow(0 1px 0 var(--media-controls-current-shadow-color, oklch(0 0 0 / 0.25)));\n }\n}\n\n/* Seek button */\n.media-default-skin .media-button--seek {\n & .media-icon__label {\n position: absolute;\n right: -1px;\n bottom: -3px;\n font-size: 0.75em;\n font-weight: 480;\n font-variant-numeric: tabular-nums;\n }\n\n &:has(.media-icon--flipped) .media-icon__label {\n right: unset;\n left: -1px;\n }\n\n @container media-controls (width < 28rem) {\n display: none;\n }\n}\n\n/* Playback rate button */\n.media-default-skin .media-button--playback-rate {\n padding: 0;\n\n &::after {\n content: attr(data-rate) \"\\00D7\";\n width: 4ch;\n font-variant-numeric: tabular-nums;\n }\n}\n\n/* ==========================================================================\n Icons\n ========================================================================== */\n\n.media-default-skin .media-icon__container {\n position: relative;\n}\n.media-default-skin .media-icon {\n display: block;\n flex-shrink: 0;\n grid-area: 1 / 1;\n width: 18px;\n height: 18px;\n transition-behavior: allow-discrete;\n transition-property: display, opacity;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.media-default-skin .media-icon--flipped {\n scale: -1 1;\n}\n\n/* ==========================================================================\n Slider\n ========================================================================== */\n\n.media-default-skin .media-slider {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n border-radius: calc(infinity * 1px);\n outline: none;\n\n &[data-orientation=\"horizontal\"] {\n min-width: 5rem;\n width: 100%;\n height: 1.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 1.25rem;\n height: 5rem;\n }\n}\n\n/* Track */\n.media-default-skin .media-slider__track {\n position: relative;\n isolation: isolate;\n overflow: hidden;\n border-radius: inherit;\n user-select: none;\n\n &[data-orientation=\"horizontal\"] {\n width: 100%;\n height: 0.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 0.25rem;\n height: 100%;\n }\n}\n\n/* Thumb */\n.media-default-skin .media-slider__thumb {\n z-index: 10;\n position: absolute;\n transform: translate(-50%, -50%);\n width: 0.625rem;\n height: 0.625rem;\n background-color: currentColor;\n border-radius: calc(infinity * 1px);\n box-shadow:\n 0 0 0 1px var(--media-controls-current-shadow-color-subtle, oklch(0 0 0 / 0.1)),\n 0 1px 3px 0 oklch(0 0 0 / 0.15),\n 0 1px 2px -1px oklch(0 0 0 / 0.15);\n opacity: 0;\n transition-property: opacity, height, width, outline-offset;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n user-select: none;\n outline: 4px solid transparent;\n outline-offset: -4px;\n\n &[data-orientation=\"horizontal\"] {\n top: 50%;\n left: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n left: 50%;\n top: calc(100% - var(--media-slider-fill));\n }\n\n &:hover,\n &:focus {\n outline-color: oklch(from currentColor l c h / 0.25);\n outline-offset: 0;\n }\n}\n\n.media-default-skin .media-slider:active .media-slider__thumb,\n.media-default-skin .media-slider__thumb--persistent {\n width: 0.75rem;\n height: 0.75rem;\n}\n\n.media-default-skin .media-slider:hover .media-slider__thumb,\n.media-default-skin .media-slider__thumb:focus-visible,\n.media-default-skin .media-slider__thumb--persistent {\n opacity: 1;\n}\n\n/* Shared track fills */\n.media-default-skin .media-slider__buffer,\n.media-default-skin .media-slider__fill {\n position: absolute;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.media-default-skin .media-slider__buffer[data-orientation=\"horizontal\"],\n.media-default-skin .media-slider__fill[data-orientation=\"horizontal\"] {\n inset-block: 0;\n left: 0;\n}\n\n.media-default-skin .media-slider__buffer[data-orientation=\"vertical\"],\n.media-default-skin .media-slider__fill[data-orientation=\"vertical\"] {\n inset-inline: 0;\n bottom: 0;\n}\n\n/* Buffer */\n.media-default-skin .media-slider__buffer {\n background-color: oklch(from currentColor l c h / 0.2);\n transition-duration: 0.25s;\n transition-timing-function: ease-out;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-buffer);\n transition-property: width;\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-buffer);\n transition-property: height;\n }\n}\n\n/* Fill */\n.media-default-skin .media-slider__fill {\n background-color: currentColor;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-fill);\n }\n}\n\n/* Time display within slider */\n.media-default-skin .media-slider__time-display {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Popups & Tooltips\n ========================================================================== */\n\n.media-default-skin .media-popup {\n margin: 0;\n border: 0;\n color: inherit;\n overflow: visible;\n transition-property: transform, scale, opacity, filter;\n transition-duration: 200ms;\n\n &[data-starting-style],\n &[data-ending-style] {\n opacity: 0;\n transform: scale(0);\n filter: blur(8px);\n }\n\n &[data-instant] {\n transition-duration: 0ms;\n }\n\n &[data-side=\"top\"] {\n transform-origin: bottom;\n }\n &[data-side=\"bottom\"] {\n transform-origin: top;\n }\n &[data-side=\"left\"] {\n transform-origin: right;\n }\n &[data-side=\"right\"] {\n transform-origin: left;\n }\n}\n\n.media-default-skin .media-popup--popover {\n --media-popover-side-offset: 0.5rem;\n}\n\n.media-default-skin .media-popup--tooltip {\n padding: 0.25rem 0.625rem;\n border-radius: calc(infinity * 1px);\n font-size: 0.75rem;\n white-space: nowrap;\n --media-tooltip-side-offset: 0.5rem;\n}\n\n.media-default-skin .media-popup--volume {\n padding: 0.625rem 0.25rem;\n border-radius: calc(infinity * 1px);\n}\n\n\n/* ==========================================================================\n Root\n ========================================================================== */\n\n.media-default-skin--audio {\n --media-border-color: oklch(0 0 0 / 0.1);\n --media-surface-background-color: oklch(1 0 0 / 0.5);\n --media-surface-inner-border-color: oklch(1 0 0 / 0.1);\n --media-surface-outer-border-color: oklch(0 0 0 / 0.05);\n --media-surface-shadow-color: oklch(0 0 0 / 0.15);\n --media-surface-backdrop-filter: blur(8px) brightness(0.98) saturate(1.2);\n\n @media (prefers-color-scheme: dark) {\n --media-border-color: oklch(1 0 0 / 0.1);\n --media-surface-background-color: oklch(0 0 0 / 0.4);\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-default-skin--audio .media-controls {\n @media (prefers-color-scheme: dark) {\n color: oklch(1 0 0);\n }\n}\n\n/* ==========================================================================\n Sliders\n ========================================================================== */\n.media-default-skin--audio .media-slider__track {\n background-color: oklch(0 0 0 / 0.1);\n\n @media (prefers-color-scheme: dark) {\n background-color: oklch(1 0 0 / 0.2);\n box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);\n }\n}\n\n" - }, - { - "id": "minimal-video-react", - "name": "Minimal Video (React)", - "platform": "react", - "style": "css", - "tsx": "import { selectError } from '@videojs/core/dom';\nimport { type ComponentProps, forwardRef, type ReactNode, useRef, type PropsWithChildren, type CSSProperties } from 'react';\n\nimport { AlertDialog, Container, usePlayer, BufferingIndicator, CaptionsButton, Controls, FullscreenButton, MuteButton, PiPButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\n\nexport interface ErrorDialogClasses {\n root?: string;\n dialog?: string;\n content?: string;\n title?: string;\n description?: string;\n actions?: string;\n close?: string;\n}\n\nexport function ErrorDialog({ classes }: { classes?: ErrorDialogClasses }): ReactNode {\n const errorState = usePlayer(selectError);\n const lastError = useRef(errorState?.error);\n if (errorState?.error) lastError.current = errorState.error;\n\n if (!errorState) return null;\n\n return (\n {\n if (!open) errorState.dismissError();\n }}\n >\n \n
\n
\n Something went wrong.\n \n {lastError.current?.message ?? 'An error occurred while trying to play the video. Please try again.'}\n \n
\n
\n OK\n
\n
\n
\n \n );\n}\n\nconst SEEK_TIME = 10;\n\nexport type MinimalVideoSkinProps = PropsWithChildren<{ style?: CSSProperties; className?: string }>;\n\nconst Button = forwardRef>(function Button({ className, ...props }, ref) {\n return \n )}\n />\n }\n />\n \n \n \n
\n\n \n (\n \n )}\n />\n }\n />\n \n Seek backward {SEEK_TIME} seconds\n \n \n\n \n (\n \n )}\n />\n }\n />\n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n )}\n />\n }\n />\n \n \n \n \n \n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n \n \n\n {/*
\n
\n An example cue\n \n

Another example cue with HTML

\n
\n
\n
*/}\n\n
\n \n );\n}\n", - "jsx": "import { selectError } from '@videojs/core/dom';\nimport { forwardRef, useRef } from 'react';\nimport { AlertDialog, Container, usePlayer, BufferingIndicator, CaptionsButton, Controls, FullscreenButton, MuteButton, PiPButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nexport function ErrorDialog({ classes }) {\n const errorState = usePlayer(selectError);\n const lastError = useRef(errorState?.error);\n if (errorState?.error)\n lastError.current = errorState.error;\n if (!errorState)\n return null;\n return ( {\n if (!open)\n errorState.dismissError();\n }}>\n \n
\n
\n Something went wrong.\n \n {lastError.current?.message ?? 'An error occurred while trying to play the video. Please try again.'}\n \n
\n
\n OK\n
\n
\n
\n
);\n}\nconst SEEK_TIME = 10;\nconst Button = forwardRef(function Button({ className, ...props }, ref) {\n return )}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n Seek backward {SEEK_TIME} seconds\n \n \n\n \n ()}/>}/>\n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n )}/>}/>\n \n \n \n \n \n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n \n \n\n {/*
\n
\n An example cue\n \n

Another example cue with HTML

\n
\n
\n
*/}\n\n
\n );\n}\n", - "css": "/* ==========================================================================\n Icon State Visibility for Video Skins\n\n Data-attribute-driven visibility rules for multi-state icon buttons.\n Uses :is() with both element selectors (for HTML custom element wrappers)\n and class selectors (for React rendered SVG elements).\n ========================================================================== */\n\n/* --- All icons hidden by default --- */\n\n.media-button--play .media-icon--restart,\n.media-button--play .media-icon--play,\n.media-button--play .media-icon--pause,\n.media-button--mute .media-icon--volume-off,\n.media-button--mute .media-icon--volume-low,\n.media-button--mute .media-icon--volume-high,\n.media-button--fullscreen .media-icon--fullscreen-enter,\n.media-button--fullscreen .media-icon--fullscreen-exit,\n.media-button--captions .media-icon--captions-off,\n.media-button--captions .media-icon--captions-on {\n display: none;\n opacity: 0;\n}\n\n/* --- Active icon per state --- */\n\n/* Play: ended → restart */\n.media-button--play[data-ended] .media-icon--restart,\n/* Play: paused (not ended) → play */\n.media-button--play:not([data-ended])[data-paused] .media-icon--play,\n/* Play: playing (not paused, not ended) → pause */\n.media-button--play:not([data-paused]):not([data-ended]) .media-icon--pause,\n/* Mute: muted → volume off */\n.media-button--mute[data-muted] .media-icon--volume-off,\n/* Mute: volume low (not muted) → volume low */\n.media-button--mute:not([data-muted])[data-volume-level=\"low\"] .media-icon--volume-low,\n/* Mute: volume high (not muted, not low) → volume high */\n.media-button--mute:not([data-muted]):not([data-volume-level=\"low\"]) .media-icon--volume-high,\n/* Fullscreen: not fullscreen → enter */\n.media-button--fullscreen:not([data-fullscreen]) .media-icon--fullscreen-enter,\n/* Fullscreen: fullscreen → exit */\n.media-button--fullscreen[data-fullscreen] .media-icon--fullscreen-exit,\n/* Captions: not active → captions off */\n.media-button--captions:not([data-active]) .media-icon--captions-off,\n/* Captions: active → captions on */\n.media-button--captions[data-active] .media-icon--captions-on {\n display: block;\n opacity: 1;\n}\n\n/* ==========================================================================\n Tooltip Label State Visibility\n\n Data-attribute-driven visibility rules for multi-state tooltip labels.\n Uses adjacent sibling selectors to match button state → tooltip content.\n ========================================================================== */\n\n/* --- All multi-state labels hidden by default --- */\n\n.media-tooltip-label {\n display: none;\n}\n\n/* --- Active label per state --- */\n\n/* Play: ended → replay */\n.media-button--play[data-ended] + media-tooltip .media-tooltip-label--replay,\n/* Play: paused (not ended) → play */\n .media-button--play:not([data-ended])[data-paused] + media-tooltip\n .media-tooltip-label--play,\n/* Play: playing (not paused, not ended) → pause */\n .media-button--play:not([data-paused]):not([data-ended]) + media-tooltip\n .media-tooltip-label--pause,\n/* Fullscreen: not fullscreen → enter */\n .media-button--fullscreen:not([data-fullscreen]) + media-tooltip\n .media-tooltip-label--enter-fullscreen,\n/* Fullscreen: fullscreen → exit */\n .media-button--fullscreen[data-fullscreen] + media-tooltip\n .media-tooltip-label--exit-fullscreen,\n/* Captions: not active → enable */\n .media-button--captions:not([data-active]) + media-tooltip\n .media-tooltip-label--enable-captions,\n/* Captions: active → disable */\n .media-button--captions[data-active] + media-tooltip\n .media-tooltip-label--disable-captions,\n/* PiP: not in pip → enter */\n media-pip-button:not([data-pip]) + media-tooltip\n .media-tooltip-label--enter-pip,\n/* PiP: in pip → exit */\n media-pip-button[data-pip] + media-tooltip\n .media-tooltip-label--exit-pip {\n display: block;\n}\n\n/* ==========================================================================\n Reset\n ========================================================================== */\n\n.media-minimal-skin *,\n.media-minimal-skin *::before,\n.media-minimal-skin *::after {\n box-sizing: border-box;\n margin: 0;\n}\n.media-minimal-skin img,\n.media-minimal-skin video,\n.media-minimal-skin svg {\n display: block;\n max-width: 100%;\n}\n.media-minimal-skin button {\n font: inherit;\n}\n@media (prefers-reduced-motion: no-preference) {\n .media-minimal-skin {\n interpolate-size: allow-keywords;\n }\n}\n\n/* ==========================================================================\n Root Container\n ========================================================================== */\n\n.media-minimal-skin {\n position: relative;\n isolation: isolate;\n display: block;\n container: media-root / inline-size;\n border-radius: var(--media-border-radius, 0.75rem);\n font-family:\n Inter Variable,\n Inter,\n ui-sans-serif,\n system-ui,\n sans-serif;\n font-size: 0.8125rem;\n line-height: 1.5;\n letter-spacing: normal;\n -webkit-font-smoothing: auto;\n -moz-osx-font-smoothing: auto;\n}\n\n/* ==========================================================================\n Media Element\n ========================================================================== */\n\n.media-minimal-skin ::slotted(video),\n.media-minimal-skin video {\n display: block;\n width: 100%;\n height: 100%;\n border-radius: var(--media-border-radius, 0.75rem);\n}\n\n/* ==========================================================================\n Poster Image\n ========================================================================== */\n\n.media-minimal-skin > img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n border-radius: inherit;\n object-fit: cover;\n transition: opacity 0.25s;\n pointer-events: none;\n\n &:not([data-visible]) {\n opacity: 0;\n }\n}\n\n/* ==========================================================================\n Overlay / Scrim\n ========================================================================== */\n\n.media-minimal-skin .media-overlay {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background-image: linear-gradient(to top, oklch(0 0 0 / 0.7), oklch(0 0 0 / 0.5) 7.5rem, oklch(0 0 0 / 0));\n backdrop-filter: blur(0) saturate(1.2) brightness(0.9);\n opacity: 0;\n transition-property: opacity, backdrop-filter;\n transition-duration: 500ms;\n transition-delay: 500ms;\n transition-timing-function: ease-out;\n pointer-events: none;\n\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n }\n}\n\n.media-minimal-skin .media-controls[data-visible] ~ .media-overlay,\n.media-minimal-skin .media-error[data-open] ~ .media-overlay {\n opacity: 1;\n transition-duration: 150ms;\n transition-delay: 0ms;\n}\n\n.media-minimal-skin .media-error[data-open] ~ .media-overlay {\n backdrop-filter: blur(8px) saturate(1.2) brightness(0.9);\n}\n\n/* ==========================================================================\n Buffering Indicator\n ========================================================================== */\n\n.media-minimal-skin .media-buffering-indicator {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n color: oklch(1 0 0);\n pointer-events: none;\n\n &[data-visible] {\n display: flex;\n }\n}\n\n/* ==========================================================================\n Error Dialog\n ========================================================================== */\n\n.media-minimal-skin .media-error {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n\n.media-minimal-skin .media-error__dialog {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n max-width: 16rem;\n padding: 1rem;\n color: oklch(1 0 0);\n font-size: 0.875rem;\n text-shadow: 0 1px 0 oklch(0 0 0 / 0.5);\n transition-property: opacity, transform;\n transition-duration: 500ms;\n transition-delay: 100ms;\n transition-timing-function: linear(\n 0,\n 0.034 1.5%,\n 0.763 9.7%,\n 1.066 13.9%,\n 1.198 19.9%,\n 1.184 21.8%,\n 0.963 37.5%,\n 0.997 50.9%,\n 1\n );\n\n /* Simple, fast transition for reduced motion users */\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n }\n}\n\n.media-minimal-skin .media-error[data-starting-style] .media-error__dialog,\n.media-minimal-skin .media-error[data-ending-style] .media-error__dialog {\n opacity: 0;\n transform: scale(0.5);\n}\n\n.media-minimal-skin .media-error__content {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n padding: 0.375rem 0;\n}\n\n.media-minimal-skin .media-error__title {\n font-weight: 600;\n line-height: 1.25;\n}\n\n.media-minimal-skin .media-error__description {\n opacity: 0.7;\n}\n\n.media-minimal-skin .media-error__actions {\n display: flex;\n gap: 0.5rem;\n\n & > * {\n flex: 1;\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-minimal-skin .media-controls {\n container: media-controls / inline-size;\n display: flex;\n align-items: center;\n --media-controls-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.25));\n --media-controls-current-shadow-color-subtle: oklch(\n from var(--media-controls-current-shadow-color) l c h /\n calc(alpha * 0.4)\n );\n text-shadow: 0 0 1px var(--media-controls-current-shadow-color);\n}\n\n/* ==========================================================================\n Time Controls & Display\n ========================================================================== */\n\n.media-minimal-skin .media-time-controls {\n display: flex;\n flex-direction: row-reverse;\n align-items: center;\n flex: 1;\n gap: 0.75rem;\n}\n.media-minimal-skin .media-time {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n.media-minimal-skin .media-time__value {\n font-variant-numeric: tabular-nums;\n}\n.media-minimal-skin .media-time__value--current,\n.media-minimal-skin .media-time__separator {\n display: none;\n}\n@container media-controls (width > 28rem) {\n .media-minimal-skin .media-time-controls {\n flex-direction: row;\n }\n .media-minimal-skin .media-time__value--duration,\n .media-minimal-skin .media-time__separator {\n color: oklch(from currentColor l c h / 0.6);\n }\n .media-minimal-skin .media-time__value--current,\n .media-minimal-skin .media-time__separator {\n display: inline;\n }\n}\n\n/* ==========================================================================\n Button Groups\n ========================================================================== */\n\n.media-minimal-skin .media-button-group {\n display: flex;\n align-items: center;\n gap: 0.075rem;\n\n @container media-root (width > 40rem) {\n gap: 0.125rem;\n }\n}\n\n/* ==========================================================================\n Buttons\n ========================================================================== */\n\n/* Base button */\n.media-minimal-skin .media-button {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0.5rem 1rem;\n background: oklch(1 0 0);\n border: none;\n border-radius: 0.5rem;\n outline: 2px solid transparent;\n outline-offset: -2px;\n color: oklch(0 0 0);\n font-weight: 500;\n text-align: center;\n text-shadow: inherit;\n transition-property: background-color, color, outline-offset, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n cursor: pointer;\n user-select: none;\n\n &:focus-visible {\n outline-color: currentColor;\n outline-offset: 2px;\n }\n\n &[disabled] {\n opacity: 0.5;\n filter: grayscale(1);\n cursor: not-allowed;\n }\n\n &[data-availability=\"unavailable\"] {\n display: none;\n }\n}\n\n/* Icon button variant */\n.media-minimal-skin .media-button--icon {\n display: grid;\n width: 2.375rem;\n padding: 0;\n aspect-ratio: 1;\n background: transparent;\n color: inherit;\n\n &:hover,\n &:focus-visible,\n &[aria-expanded=\"true\"] {\n color: oklch(from currentColor l c h / 0.8);\n text-decoration: none;\n }\n\n &:active {\n transform: scale(0.9);\n }\n\n & .media-icon {\n filter: drop-shadow(0 1px 0 var(--media-controls-current-shadow-color, oklch(0 0 0 / 0.25)));\n }\n}\n\n/* Seek button */\n.media-minimal-skin .media-button--seek {\n & .media-icon__label {\n position: absolute;\n right: -1px;\n bottom: -3px;\n font-size: 0.75em;\n font-weight: 480;\n font-variant-numeric: tabular-nums;\n }\n\n &:has(.media-icon--flipped) .media-icon__label {\n right: unset;\n left: -1px;\n }\n\n @container media-controls (width < 28rem) {\n display: none;\n }\n}\n\n/* Playback rate button */\n.media-minimal-skin .media-button--playback-rate {\n padding: 0;\n\n &::after {\n content: attr(data-rate) \"\\00D7\";\n width: 4ch;\n font-variant-numeric: tabular-nums;\n }\n}\n\n/* ==========================================================================\n Icons\n ========================================================================== */\n\n.media-minimal-skin .media-icon__container {\n position: relative;\n}\n.media-minimal-skin .media-icon {\n display: block;\n flex-shrink: 0;\n grid-area: 1 / 1;\n width: 18px;\n height: 18px;\n transition-behavior: allow-discrete;\n transition-property: display, opacity;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.media-minimal-skin .media-icon--flipped {\n scale: -1 1;\n}\n\n/* ==========================================================================\n Slider\n ========================================================================== */\n\n.media-minimal-skin .media-slider {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n border-radius: calc(infinity * 1px);\n outline: none;\n\n &[data-orientation=\"horizontal\"] {\n min-width: 5rem;\n width: 100%;\n height: 1.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 1.25rem;\n height: 4.5rem;\n }\n}\n\n/* Track */\n.media-minimal-skin .media-slider__track {\n position: relative;\n isolation: isolate;\n overflow: hidden;\n border-radius: inherit;\n user-select: none;\n background-color: oklch(from currentColor l c h / 0.2);\n\n &[data-orientation=\"horizontal\"] {\n width: 100%;\n height: 0.1875rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 0.1875rem;\n height: 100%;\n }\n}\n\n/* Thumb */\n.media-minimal-skin .media-slider__thumb {\n position: absolute;\n transform: translate(-50%, -50%);\n z-index: 10;\n width: 0.75rem;\n height: 0.75rem;\n background-color: currentColor;\n border-radius: calc(infinity * 1px);\n box-shadow:\n 0 0 0 1px var(--media-controls-current-shadow-color-subtle, oklch(0 0 0 / 0.1)),\n 0 1px 3px 0 oklch(0 0 0 / 0.15),\n 0 1px 2px -1px oklch(0 0 0 / 0.15);\n opacity: 0;\n scale: 0.7;\n transform-origin: center;\n transition-property: opacity, scale, outline-offset;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n user-select: none;\n outline: 2px solid transparent;\n outline-offset: -2px;\n\n &[data-orientation=\"horizontal\"] {\n top: 50%;\n left: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n left: 50%;\n top: calc(100% - var(--media-slider-fill));\n }\n\n &:focus-visible {\n outline-color: currentColor;\n outline-offset: 2px;\n }\n}\n\n.media-minimal-skin .media-slider:hover .media-slider__thumb,\n.media-minimal-skin .media-slider:focus-within .media-slider__thumb,\n.media-minimal-skin .media-slider__thumb--persistent {\n opacity: 1;\n scale: 1;\n}\n\n/* Shared track fills */\n.media-minimal-skin .media-slider__buffer,\n.media-minimal-skin .media-slider__fill {\n position: absolute;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.media-minimal-skin .media-slider__buffer[data-orientation=\"horizontal\"],\n.media-minimal-skin .media-slider__fill[data-orientation=\"horizontal\"] {\n inset-block: 0;\n left: 0;\n}\n\n.media-minimal-skin .media-slider__buffer[data-orientation=\"vertical\"],\n.media-minimal-skin .media-slider__fill[data-orientation=\"vertical\"] {\n inset-inline: 0;\n bottom: 0;\n}\n\n/* Buffer */\n.media-minimal-skin .media-slider__buffer {\n background-color: oklch(from currentColor l c h / 0.2);\n transition-duration: 0.25s;\n transition-timing-function: ease-out;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-buffer);\n transition-property: width;\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-buffer);\n transition-property: height;\n }\n}\n\n/* Fill */\n.media-minimal-skin .media-slider__fill {\n background-color: currentColor;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-fill);\n }\n}\n\n/* Time display within slider */\n.media-minimal-skin .media-slider__time-display {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Popups & Animations\n ========================================================================== */\n\n.media-minimal-skin .media-popup {\n margin: 0;\n border: 0;\n color: inherit;\n overflow: visible;\n transition-property: transform, scale, opacity, filter;\n transition-duration: 200ms;\n\n &[data-starting-style],\n &[data-ending-style] {\n opacity: 0;\n transform: scale(0);\n filter: blur(8px);\n }\n\n &[data-instant] {\n transition-duration: 0ms;\n }\n\n &[data-side=\"top\"] {\n transform-origin: bottom;\n }\n &[data-side=\"bottom\"] {\n transform-origin: top;\n }\n &[data-side=\"left\"] {\n transform-origin: right;\n }\n &[data-side=\"right\"] {\n transform-origin: left;\n }\n}\n\n.media-minimal-skin .media-popup--tooltip {\n padding: 0.25rem 0.5rem;\n border-radius: 0.25rem;\n background-color: oklch(1 0 0 / 0.1);\n backdrop-filter: blur(64px) brightness(0.9) saturate(1.5);\n box-shadow:\n 0 4px 6px -1px oklch(0 0 0 / 0.1),\n 0 2px 4px -2px oklch(0 0 0 / 0.1);\n font-size: 0.75rem;\n white-space: nowrap;\n --media-tooltip-side-offset: 0.5rem;\n\n @media (prefers-reduced-transparency: reduce) {\n background-color: oklch(0 0 0 / 0.7);\n }\n\n @media (prefers-contrast: more) {\n background-color: oklch(0 0 0 / 0.9);\n }\n}\n\n/* ==========================================================================\n Captions\n ========================================================================== */\n\n.media-minimal-skin .media-captions {\n position: absolute;\n inset: auto 1rem 1.5rem 1rem;\n z-index: 20;\n font-size: 1rem;\n text-wrap: balance;\n pointer-events: none;\n\n @container media-root (width > 20rem) {\n font-size: 1.5rem;\n }\n\n @container media-root (width > 48rem) {\n font-size: 1.875rem;\n }\n\n @container media-root (width > 80rem) {\n font-size: 2.25rem;\n }\n}\n\n.media-minimal-skin .media-captions__container {\n display: flex;\n flex-direction: column;\n align-items: center;\n max-width: 42ch;\n margin: 0 auto;\n text-align: center;\n}\n\n.media-minimal-skin .media-captions__text {\n display: block;\n padding: 0.125rem 0.5rem;\n color: oklch(1 0 0);\n text-shadow:\n 0 0 1px oklch(0 0 0 / 0.7),\n 0 0 8px oklch(0 0 0 / 0.7);\n text-align: center;\n white-space: pre-wrap;\n line-height: 1.2;\n\n @media (prefers-contrast: more) {\n background: oklch(0 0 0 / 0.7);\n text-shadow: none;\n box-decoration-break: clone;\n }\n\n & > * {\n display: inline;\n }\n}\n\n/* Caption shifting styles (custom and native) */\n.media-minimal-skin {\n --media-caption-track-delay: 600ms;\n --media-caption-track-y: -0.5rem;\n\n &:has(.media-controls[data-visible]) {\n --media-caption-track-delay: 25ms;\n --media-caption-track-y: -3rem;\n }\n}\n\n.media-minimal-skin .media-captions,\n.media-minimal-skin video::-webkit-media-text-track-container {\n /* NOTE: The delay must account for the controls delay/duration */\n transition: transform 150ms ease-out;\n transition-delay: var(--media-caption-track-delay);\n}\n\n.media-minimal-skin video::-webkit-media-text-track-container {\n transform: translateY(var(--media-caption-track-y)) scale(0.98);\n z-index: 1;\n font-family: inherit;\n}\n\n/* When controls are visible, shift captions up to avoid overlap */\n.media-minimal-skin .media-controls[data-visible] ~ .media-captions {\n transform: translateY(calc(var(--media-caption-track-y) - 0.5rem));\n}\n\n@media (prefers-reduced-motion: reduce) {\n .media-minimal-skin .media-captions,\n .media-minimal-skin video::-webkit-media-text-track-container {\n transition-duration: 50ms;\n }\n}\n\n\n/* ==========================================================================\n Root\n ========================================================================== */\n\n.media-minimal-skin--video {\n background: oklch(0 0 0);\n\n /* Border ring */\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n z-index: 10;\n border-radius: inherit;\n box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.15);\n pointer-events: none;\n\n @media (prefers-color-scheme: dark) {\n box-shadow: inset 0 0 0 1px oklch(1 0 0 / 0.15);\n }\n }\n\n /* Fullscreen */\n &:fullscreen {\n border-radius: 0;\n }\n}\n\n/* ==========================================================================\n Controls (hide/show behavior)\n ========================================================================== */\n\n.media-minimal-skin--video .media-controls {\n position: absolute;\n bottom: 0;\n inset-inline: 0;\n z-index: 10;\n gap: 0.5rem;\n padding: 2rem 0.375rem 0.375rem 0.375rem;\n will-change: transform, filter, opacity;\n transition-property: transform, filter, opacity;\n transition-duration: 75ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n color: oklch(1 0 0);\n\n @container media-root (width > 40rem) {\n gap: 0.875rem;\n padding: 2.5rem 0.75rem 0.75rem 0.75rem;\n }\n\n &:not([data-visible]) {\n opacity: 0;\n transform: translateY(100%);\n filter: blur(8px);\n transition-duration: 500ms;\n transition-delay: 500ms;\n pointer-events: none;\n\n @media (prefers-reduced-motion: reduce) {\n scale: 1;\n transform: translateY(0);\n filter: blur(0);\n transition-duration: 100ms;\n }\n }\n}\n\n/* ==========================================================================\n Sliders\n ========================================================================== */\n\n.media-minimal-skin--video .media-slider__track {\n box-shadow: 0 0 0 1px oklch(0 0 0 / 0.05);\n}\n\n/* ==========================================================================\n Popups & Animations\n ========================================================================== */\n\n.media-minimal-skin--video .media-popup--volume {\n --media-popover-side-offset: 0.5rem;\n background: transparent;\n padding: 0.25rem;\n}\n\n" - }, - { - "id": "minimal-audio-react", - "name": "Minimal Audio (React)", - "platform": "react", - "style": "css", - "tsx": "import { type ComponentProps, forwardRef, type ReactNode, type PropsWithChildren, type CSSProperties } from 'react';\n\nimport { Container, usePlayer, MuteButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nconst SEEK_TIME = 10;\n\nexport type MinimalAudioSkinProps = PropsWithChildren<{ style?: CSSProperties; className?: string }>;\n\nconst Button = forwardRef>(function Button({ className, ...props }, ref) {\n return \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n Seek backward {SEEK_TIME} seconds\n \n \n\n \n (\n \n )}\n />\n }\n />\n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n )}\n />\n }\n />\n \n \n \n \n \n \n \n \n \n \n
\n \n );\n}\n", - "jsx": "import { forwardRef } from 'react';\nimport { Container, usePlayer, MuteButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nconst SEEK_TIME = 10;\nconst Button = forwardRef(function Button({ className, ...props }, ref) {\n return )}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n Seek backward {SEEK_TIME} seconds\n \n \n\n \n ()}/>}/>\n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n )}/>}/>\n \n \n \n \n \n \n \n \n \n \n
\n );\n}\n", - "css": "/* ==========================================================================\n Icon State Visibility for Audio Skins\n\n Data-attribute-driven visibility rules for multi-state icon buttons.\n Uses :is() with both element selectors (for HTML custom element wrappers)\n and class selectors (for React rendered SVG elements).\n ========================================================================== */\n\n/* --- All icons hidden by default --- */\n\n.media-button--play .media-icon--restart,\n.media-button--play .media-icon--play,\n.media-button--play .media-icon--pause,\n.media-button--mute .media-icon--volume-off,\n.media-button--mute .media-icon--volume-low,\n.media-button--mute .media-icon--volume-high {\n display: none;\n opacity: 0;\n}\n\n/* --- Active icon per state --- */\n\n/* Play: ended → restart */\n.media-button--play[data-ended] .media-icon--restart,\n/* Play: paused (not ended) → play */\n.media-button--play:not([data-ended])[data-paused] .media-icon--play,\n/* Play: playing (not paused, not ended) → pause */\n.media-button--play:not([data-paused]):not([data-ended]) .media-icon--pause,\n/* Mute: muted → volume off */\n.media-button--mute[data-muted] .media-icon--volume-off,\n/* Mute: volume low (not muted) → volume low */\n.media-button--mute:not([data-muted])[data-volume-level=\"low\"] .media-icon--volume-low,\n/* Mute: volume high (not muted, not low) → volume high */\n.media-button--mute:not([data-muted]):not([data-volume-level=\"low\"]) .media-icon--volume-high {\n display: block;\n opacity: 1;\n}\n\n/* ==========================================================================\n Tooltip Label State Visibility\n\n Data-attribute-driven visibility rules for multi-state tooltip labels.\n Uses adjacent sibling selectors to match button state → tooltip content.\n ========================================================================== */\n\n/* --- All multi-state labels hidden by default --- */\n\n.media-tooltip-label {\n display: none;\n}\n\n/* --- Active label per state --- */\n\n/* Play: ended → replay */\n.media-button--play[data-ended] + media-tooltip .media-tooltip-label--replay,\n/* Play: paused (not ended) → play */\n .media-button--play:not([data-ended])[data-paused] + media-tooltip\n .media-tooltip-label--play,\n/* Play: playing (not paused, not ended) → pause */\n .media-button--play:not([data-paused]):not([data-ended]) + media-tooltip\n .media-tooltip-label--pause,\n/* Fullscreen: not fullscreen → enter */\n .media-button--fullscreen:not([data-fullscreen]) + media-tooltip\n .media-tooltip-label--enter-fullscreen,\n/* Fullscreen: fullscreen → exit */\n .media-button--fullscreen[data-fullscreen] + media-tooltip\n .media-tooltip-label--exit-fullscreen,\n/* Captions: not active → enable */\n .media-button--captions:not([data-active]) + media-tooltip\n .media-tooltip-label--enable-captions,\n/* Captions: active → disable */\n .media-button--captions[data-active] + media-tooltip\n .media-tooltip-label--disable-captions,\n/* PiP: not in pip → enter */\n media-pip-button:not([data-pip]) + media-tooltip\n .media-tooltip-label--enter-pip,\n/* PiP: in pip → exit */\n media-pip-button[data-pip] + media-tooltip\n .media-tooltip-label--exit-pip {\n display: block;\n}\n\n/* ==========================================================================\n Reset\n ========================================================================== */\n\n.media-minimal-skin *,\n.media-minimal-skin *::before,\n.media-minimal-skin *::after {\n box-sizing: border-box;\n margin: 0;\n}\n.media-minimal-skin img,\n.media-minimal-skin video,\n.media-minimal-skin svg {\n display: block;\n max-width: 100%;\n}\n.media-minimal-skin button {\n font: inherit;\n}\n@media (prefers-reduced-motion: no-preference) {\n .media-minimal-skin {\n interpolate-size: allow-keywords;\n }\n}\n\n/* ==========================================================================\n Root Container\n ========================================================================== */\n\n.media-minimal-skin {\n position: relative;\n isolation: isolate;\n display: block;\n container: media-root / inline-size;\n border-radius: var(--media-border-radius, 0.75rem);\n font-family:\n Inter Variable,\n Inter,\n ui-sans-serif,\n system-ui,\n sans-serif;\n font-size: 0.8125rem;\n line-height: 1.5;\n letter-spacing: normal;\n -webkit-font-smoothing: auto;\n -moz-osx-font-smoothing: auto;\n}\n\n/* ==========================================================================\n Buffering Indicator\n ========================================================================== */\n\n.media-minimal-skin .media-buffering-indicator {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n color: oklch(1 0 0);\n pointer-events: none;\n\n &[data-visible] {\n display: flex;\n }\n}\n\n/* ==========================================================================\n Error Dialog\n ========================================================================== */\n\n.media-minimal-skin .media-error {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n\n.media-minimal-skin .media-error__dialog {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n max-width: 16rem;\n padding: 1rem;\n color: oklch(1 0 0);\n font-size: 0.875rem;\n text-shadow: 0 1px 0 oklch(0 0 0 / 0.5);\n transition-property: opacity, transform;\n transition-duration: 500ms;\n transition-delay: 100ms;\n transition-timing-function: linear(\n 0,\n 0.034 1.5%,\n 0.763 9.7%,\n 1.066 13.9%,\n 1.198 19.9%,\n 1.184 21.8%,\n 0.963 37.5%,\n 0.997 50.9%,\n 1\n );\n\n /* Simple, fast transition for reduced motion users */\n @media (prefers-reduced-motion: reduce) {\n transition-duration: 100ms;\n transition-delay: 0ms;\n transition-timing-function: ease-out;\n }\n}\n\n.media-minimal-skin .media-error[data-starting-style] .media-error__dialog,\n.media-minimal-skin .media-error[data-ending-style] .media-error__dialog {\n opacity: 0;\n transform: scale(0.5);\n}\n\n.media-minimal-skin .media-error__content {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n padding: 0.375rem 0;\n}\n\n.media-minimal-skin .media-error__title {\n font-weight: 600;\n line-height: 1.25;\n}\n\n.media-minimal-skin .media-error__description {\n opacity: 0.7;\n}\n\n.media-minimal-skin .media-error__actions {\n display: flex;\n gap: 0.5rem;\n\n & > * {\n flex: 1;\n }\n}\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-minimal-skin .media-controls {\n container: media-controls / inline-size;\n display: flex;\n align-items: center;\n --media-controls-current-shadow-color: oklch(from currentColor 0 0 0 / clamp(0, calc((l - 0.5) * 0.5), 0.25));\n --media-controls-current-shadow-color-subtle: oklch(\n from var(--media-controls-current-shadow-color) l c h /\n calc(alpha * 0.4)\n );\n text-shadow: 0 0 1px var(--media-controls-current-shadow-color);\n}\n\n/* ==========================================================================\n Time Controls & Display\n ========================================================================== */\n\n.media-minimal-skin .media-time-controls {\n display: flex;\n flex-direction: row-reverse;\n align-items: center;\n flex: 1;\n gap: 0.75rem;\n}\n.media-minimal-skin .media-time {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n.media-minimal-skin .media-time__value {\n font-variant-numeric: tabular-nums;\n}\n.media-minimal-skin .media-time__value--current,\n.media-minimal-skin .media-time__separator {\n display: none;\n}\n@container media-controls (width > 28rem) {\n .media-minimal-skin .media-time-controls {\n flex-direction: row;\n }\n .media-minimal-skin .media-time__value--duration,\n .media-minimal-skin .media-time__separator {\n color: oklch(from currentColor l c h / 0.6);\n }\n .media-minimal-skin .media-time__value--current,\n .media-minimal-skin .media-time__separator {\n display: inline;\n }\n}\n\n/* ==========================================================================\n Button Groups\n ========================================================================== */\n\n.media-minimal-skin .media-button-group {\n display: flex;\n align-items: center;\n gap: 0.075rem;\n\n @container media-root (width > 40rem) {\n gap: 0.125rem;\n }\n}\n\n/* ==========================================================================\n Buttons\n ========================================================================== */\n\n/* Base button */\n.media-minimal-skin .media-button {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0.5rem 1rem;\n background: oklch(1 0 0);\n border: none;\n border-radius: 0.5rem;\n outline: 2px solid transparent;\n outline-offset: -2px;\n color: oklch(0 0 0);\n font-weight: 500;\n text-align: center;\n text-shadow: inherit;\n transition-property: background-color, color, outline-offset, transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n cursor: pointer;\n user-select: none;\n\n &:focus-visible {\n outline-color: currentColor;\n outline-offset: 2px;\n }\n\n &[disabled] {\n opacity: 0.5;\n filter: grayscale(1);\n cursor: not-allowed;\n }\n\n &[data-availability=\"unavailable\"] {\n display: none;\n }\n}\n\n/* Icon button variant */\n.media-minimal-skin .media-button--icon {\n display: grid;\n width: 2.375rem;\n padding: 0;\n aspect-ratio: 1;\n background: transparent;\n color: inherit;\n\n &:hover,\n &:focus-visible,\n &[aria-expanded=\"true\"] {\n color: oklch(from currentColor l c h / 0.8);\n text-decoration: none;\n }\n\n &:active {\n transform: scale(0.9);\n }\n\n & .media-icon {\n filter: drop-shadow(0 1px 0 var(--media-controls-current-shadow-color, oklch(0 0 0 / 0.25)));\n }\n}\n\n/* Seek button */\n.media-minimal-skin .media-button--seek {\n & .media-icon__label {\n position: absolute;\n right: -1px;\n bottom: -3px;\n font-size: 0.75em;\n font-weight: 480;\n font-variant-numeric: tabular-nums;\n }\n\n &:has(.media-icon--flipped) .media-icon__label {\n right: unset;\n left: -1px;\n }\n\n @container media-controls (width < 28rem) {\n display: none;\n }\n}\n\n/* Playback rate button */\n.media-minimal-skin .media-button--playback-rate {\n padding: 0;\n\n &::after {\n content: attr(data-rate) \"\\00D7\";\n width: 4ch;\n font-variant-numeric: tabular-nums;\n }\n}\n\n/* ==========================================================================\n Icons\n ========================================================================== */\n\n.media-minimal-skin .media-icon__container {\n position: relative;\n}\n.media-minimal-skin .media-icon {\n display: block;\n flex-shrink: 0;\n grid-area: 1 / 1;\n width: 18px;\n height: 18px;\n transition-behavior: allow-discrete;\n transition-property: display, opacity;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n.media-minimal-skin .media-icon--flipped {\n scale: -1 1;\n}\n\n/* ==========================================================================\n Slider\n ========================================================================== */\n\n.media-minimal-skin .media-slider {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n border-radius: calc(infinity * 1px);\n outline: none;\n\n &[data-orientation=\"horizontal\"] {\n min-width: 5rem;\n width: 100%;\n height: 1.25rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 1.25rem;\n height: 4.5rem;\n }\n}\n\n/* Track */\n.media-minimal-skin .media-slider__track {\n position: relative;\n isolation: isolate;\n overflow: hidden;\n border-radius: inherit;\n user-select: none;\n background-color: oklch(from currentColor l c h / 0.2);\n\n &[data-orientation=\"horizontal\"] {\n width: 100%;\n height: 0.1875rem;\n }\n\n &[data-orientation=\"vertical\"] {\n width: 0.1875rem;\n height: 100%;\n }\n}\n\n/* Thumb */\n.media-minimal-skin .media-slider__thumb {\n position: absolute;\n transform: translate(-50%, -50%);\n z-index: 10;\n width: 0.75rem;\n height: 0.75rem;\n background-color: currentColor;\n border-radius: calc(infinity * 1px);\n box-shadow:\n 0 0 0 1px var(--media-controls-current-shadow-color-subtle, oklch(0 0 0 / 0.1)),\n 0 1px 3px 0 oklch(0 0 0 / 0.15),\n 0 1px 2px -1px oklch(0 0 0 / 0.15);\n opacity: 0;\n scale: 0.7;\n transform-origin: center;\n transition-property: opacity, scale, outline-offset;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n user-select: none;\n outline: 2px solid transparent;\n outline-offset: -2px;\n\n &[data-orientation=\"horizontal\"] {\n top: 50%;\n left: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n left: 50%;\n top: calc(100% - var(--media-slider-fill));\n }\n\n &:focus-visible {\n outline-color: currentColor;\n outline-offset: 2px;\n }\n}\n\n.media-minimal-skin .media-slider:hover .media-slider__thumb,\n.media-minimal-skin .media-slider:focus-within .media-slider__thumb,\n.media-minimal-skin .media-slider__thumb--persistent {\n opacity: 1;\n scale: 1;\n}\n\n/* Shared track fills */\n.media-minimal-skin .media-slider__buffer,\n.media-minimal-skin .media-slider__fill {\n position: absolute;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.media-minimal-skin .media-slider__buffer[data-orientation=\"horizontal\"],\n.media-minimal-skin .media-slider__fill[data-orientation=\"horizontal\"] {\n inset-block: 0;\n left: 0;\n}\n\n.media-minimal-skin .media-slider__buffer[data-orientation=\"vertical\"],\n.media-minimal-skin .media-slider__fill[data-orientation=\"vertical\"] {\n inset-inline: 0;\n bottom: 0;\n}\n\n/* Buffer */\n.media-minimal-skin .media-slider__buffer {\n background-color: oklch(from currentColor l c h / 0.2);\n transition-duration: 0.25s;\n transition-timing-function: ease-out;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-buffer);\n transition-property: width;\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-buffer);\n transition-property: height;\n }\n}\n\n/* Fill */\n.media-minimal-skin .media-slider__fill {\n background-color: currentColor;\n\n &[data-orientation=\"horizontal\"] {\n width: var(--media-slider-fill);\n }\n\n &[data-orientation=\"vertical\"] {\n height: var(--media-slider-fill);\n }\n}\n\n/* Time display within slider */\n.media-minimal-skin .media-slider__time-display {\n font-variant-numeric: tabular-nums;\n}\n\n/* ==========================================================================\n Popups & Animations\n ========================================================================== */\n\n.media-minimal-skin .media-popup {\n margin: 0;\n border: 0;\n color: inherit;\n overflow: visible;\n transition-property: transform, scale, opacity, filter;\n transition-duration: 200ms;\n\n &[data-starting-style],\n &[data-ending-style] {\n opacity: 0;\n transform: scale(0);\n filter: blur(8px);\n }\n\n &[data-instant] {\n transition-duration: 0ms;\n }\n\n &[data-side=\"top\"] {\n transform-origin: bottom;\n }\n &[data-side=\"bottom\"] {\n transform-origin: top;\n }\n &[data-side=\"left\"] {\n transform-origin: right;\n }\n &[data-side=\"right\"] {\n transform-origin: left;\n }\n}\n\n.media-minimal-skin .media-popup--tooltip {\n padding: 0.25rem 0.5rem;\n border-radius: 0.25rem;\n background-color: oklch(1 0 0 / 0.1);\n backdrop-filter: blur(64px) brightness(0.9) saturate(1.5);\n box-shadow:\n 0 4px 6px -1px oklch(0 0 0 / 0.1),\n 0 2px 4px -2px oklch(0 0 0 / 0.1);\n font-size: 0.75rem;\n white-space: nowrap;\n --media-tooltip-side-offset: 0.5rem;\n\n @media (prefers-reduced-transparency: reduce) {\n background-color: oklch(0 0 0 / 0.7);\n }\n\n @media (prefers-contrast: more) {\n background-color: oklch(0 0 0 / 0.9);\n }\n}\n\n\n/* ==========================================================================\n Controls\n ========================================================================== */\n\n.media-minimal-skin--audio {\n --media-controls-background-color: oklch(1 0 0);\n --media-controls-border-color: oklch(0 0 0 / 0.1);\n --media-controls-text-color: oklch(0 0 0);\n\n @media (prefers-color-scheme: dark) {\n --media-controls-background-color: oklch(0 0 0);\n --media-controls-border-color: oklch(1 0 0 / 0.1);\n --media-controls-text-color: oklch(1 0 0);\n }\n}\n.media-minimal-skin--audio .media-controls {\n gap: 0.5rem;\n padding: 0.375rem;\n background-color: var(--media-controls-background-color);\n backdrop-filter: blur(8px) brightness(0.98) saturate(1.2);\n border-radius: var(--media-border-radius, 0.75rem);\n color: var(--media-controls-text-color);\n box-shadow: 0 0 0 1px var(--media-controls-border-color);\n}\n\n/* ==========================================================================\n Popups & Animations\n ========================================================================== */\n\n.media-minimal-skin--audio .media-popup--volume {\n background: linear-gradient(to left, var(--media-controls-background-color) 80%, transparent 100%);\n padding: 0.5rem 0 0.5rem 4rem;\n --media-popover-side-offset: 0.75rem;\n}\n\n" - }, - { - "id": "default-video-react-tailwind", - "name": "Default Video (React + Tailwind)", - "platform": "react", - "style": "tailwind", - "tsx": "import { selectError } from '@videojs/core/dom';\nimport { type ComponentProps, forwardRef, type ReactNode, useRef, type PropsWithChildren, type CSSProperties } from 'react';\n\nimport { AlertDialog, Container, usePlayer, BufferingIndicator, CaptionsButton, Controls, FullscreenButton, MuteButton, PiPButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\n\nexport interface ErrorDialogClasses {\n root?: string;\n dialog?: string;\n content?: string;\n title?: string;\n description?: string;\n actions?: string;\n close?: string;\n}\n\nexport function ErrorDialog({ classes }: { classes?: ErrorDialogClasses }): ReactNode {\n const errorState = usePlayer(selectError);\n const lastError = useRef(errorState?.error);\n if (errorState?.error) lastError.current = errorState.error;\n\n if (!errorState) return null;\n\n return (\n {\n if (!open) errorState.dismissError();\n }}\n >\n \n
\n
\n Something went wrong.\n \n {lastError.current?.message ?? 'An error occurred while trying to play the video. Please try again.'}\n \n
\n
\n OK\n
\n
\n
\n \n );\n}\n\n\nconst bufferingIndicator = {\n root: \"absolute inset-0 hidden items-center justify-center pointer-events-none text-white data-visible:flex\",\n container: \"p-1 rounded-full ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/10 backdrop-brightness-90 backdrop-saturate-150 backdrop-blur-3xl ring-white/5 shadow-black/10 after:ring-black/15 [@media(prefers-reduced-transparency:reduce)]:bg-black/70 contrast-more:bg-black/90\",\n};\nconst button = {\n base: \"items-center justify-center shrink-0 border-none cursor-pointer select-none text-center font-medium outline-2 outline-transparent -outline-offset-2 transition-[background-color,color,outline-offset,scale] duration-150 ease-out disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-blue-500 focus-visible:outline-offset-2 data-[availability=unavailable]:hidden\",\n icon: \"grid w-[2.125rem] aspect-square bg-transparent rounded-full text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 active:scale-90\",\n default: \"flex py-2 px-4 bg-white rounded-full text-black\",\n};\nconst controls = \"peer/controls @container/media-controls p-[0.175rem] flex items-center gap-[0.075rem] rounded-full [--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.25))] [--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))] text-shadow-[0_0_1px_var(--media-controls-current-shadow-color)] @2xl/media-root:p-1 @2xl/media-root:gap-0.5 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/10 backdrop-brightness-90 backdrop-saturate-150 backdrop-blur-3xl ring-white/5 shadow-black/10 after:ring-black/15 [@media(prefers-reduced-transparency:reduce)]:bg-black/70 contrast-more:bg-black/90 absolute bottom-3 inset-x-3 text-white z-10 will-change-[scale,transform,filter,opacity] transition-[scale,transform,filter,opacity] ease-out delay-0 duration-100 origin-bottom not-data-visible:pointer-events-none not-data-visible:blur-sm not-data-visible:scale-90 not-data-visible:opacity-0 not-data-visible:delay-500 not-data-visible:duration-300 motion-reduce:not-data-visible:duration-100 motion-reduce:not-data-visible:blur-none motion-reduce:not-data-visible:scale-100\";\nconst error = {\n root: \"peer/error group/error flex absolute inset-0 z-20 items-center justify-center\",\n dialog: \"flex flex-col gap-3 max-w-72 p-3 rounded-[1.75rem] text-white text-sm transition-[opacity,scale,transform] duration-500 delay-100 ease-[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)] 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 motion-reduce:duration-100 motion-reduce:ease-out motion-reduce:delay-0 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/10 backdrop-brightness-90 backdrop-saturate-150 backdrop-blur-3xl ring-white/5 shadow-black/10 after:ring-black/15 [@media(prefers-reduced-transparency:reduce)]:bg-black/70 contrast-more:bg-black/90\",\n content: \"flex flex-col gap-2 px-2 pt-2 pb-1.5\",\n title: \"font-semibold leading-tight\",\n description: \"opacity-70\",\n actions: \"flex gap-2 *:flex-1\",\n};\nconst icon = \"block [grid-area:1/1] size-4.5 shrink-0 drop-shadow-[0_1px_0_var(--media-controls-current-shadow-color,oklch(0_0_0/0.25))] transition-discrete transition-[display,opacity] duration-150 ease-out\";\nconst iconContainer = \"relative\";\nconst iconFlipped = \"[scale:-1_1]\";\nconst iconState = {\n play: {\n button: \"group\",\n restart: \"hidden opacity-0 group-data-ended:block group-data-ended:opacity-100\",\n play: \"hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100\",\n pause: \"hidden opacity-0 group-not-data-paused:group-not-data-ended:block group-not-data-paused:group-not-data-ended:opacity-100\",\n },\n mute: {\n button: \"group\",\n volumeOff: \"hidden opacity-0 group-data-muted:block group-data-muted:opacity-100\",\n 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\",\n 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\",\n },\n fullscreen: {\n button: \"group\",\n enter: \"hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100\",\n exit: \"hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100\",\n },\n captions: {\n button: \"group\",\n off: \"hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100\",\n on: \"hidden opacity-0 group-data-active:block group-data-active:opacity-100\",\n },\n};\nconst 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 to-transparent backdrop-blur-none backdrop-saturate-120 backdrop-brightness-90 transition-[opacity,backdrop-filter] ease-out duration-300 delay-500 peer-data-visible/controls:opacity-100 peer-data-visible/controls:duration-150 peer-data-visible/controls:delay-0 peer-data-open/error:opacity-100 peer-data-open/error:duration-150 peer-data-open/error:delay-0 peer-data-open/error:backdrop-blur motion-reduce:duration-100\";\nconst playbackRate = {\n button: \"after:content-[attr(data-rate)_'×'] after:w-[4ch] after:tabular-nums\",\n};\nconst popup = {\n base: \"m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-200 data-starting-style:opacity-0 data-starting-style:scale-0 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-0 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 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/10 backdrop-brightness-90 backdrop-saturate-150 backdrop-blur-3xl ring-white/5 shadow-black/10 after:ring-black/15 [@media(prefers-reduced-transparency:reduce)]:bg-black/70 contrast-more:bg-black/90\",\n popover: \"[--media-popover-side-offset:0.5rem]\",\n tooltip: \"py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap [--media-tooltip-side-offset:0.5rem]\",\n volume: \"py-2.5 px-1 rounded-full\",\n};\nconst root = () => \"block relative isolate @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 **:box-border **:m-0 [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] [&:fullscreen]:rounded-none bg-black 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/10 [&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&>img]:absolute [&>img]:inset-0 [&>img]:w-full [&>img]:h-full [&>img]:rounded-[inherit] [&>img]:object-cover [&>img]:pointer-events-none [&>img]:transition-opacity [&>img]:duration-250 [&>img:not([data-visible])]:opacity-0 [--media-caption-track-delay:600ms] [--media-caption-track-y:-0.5rem] has-[[data-controls][data-visible]]:[--media-caption-track-delay:25ms] has-[[data-controls][data-visible]]:[--media-caption-track-y:-3.5rem] [&_video::-webkit-media-text-track-container]:transition-transform [&_video::-webkit-media-text-track-container]:duration-150 [&_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] motion-reduce:[&_video::-webkit-media-text-track-container]:duration-50 [&:fullscreen]:rounded-none\";\nconst seek = {\n button: \"@max-md/media-controls:hidden\",\n label: \"text-[0.75em] font-[480] tabular-nums\",\n labelForward: \"absolute -right-px -bottom-0.75\",\n labelBackward: \"absolute -left-px -bottom-0.75\",\n};\nconst slider = {\n root: \"group/slider relative flex flex-1 items-center justify-center rounded-full outline-none data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5 data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-20\",\n 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 shadow-[0_0_0_1px_oklch(0_0_0/0.05)]\",\n fill: {\n base: \"absolute rounded-[inherit] pointer-events-none\",\n 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)\",\n 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)\",\n },\n thumb: {\n base: \"z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-controls-current-shadow-color-subtle,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/25 hover:outline-offset-0 focus-visible:outline-current/25 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))]\",\n persistent: \"size-3\",\n interactive: \"size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3\",\n },\n};\nconst time = {\n group: \"@container/media-time flex items-center flex-1 gap-3 px-2\",\n current: \"hidden @2xs/media-time:block tabular-nums\",\n duration: \"tabular-nums\",\n};\n\ntype VideoSkinProps = PropsWithChildren<{ style?: CSSProperties; className?: string }>;\nconst SEEK_TIME = 10;\n\n/* --------------------------------------- Components ---------------------------------------- */\n\nconst Button = forwardRef & { variant?: 'icon' }>(function Button(\n { className, variant, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst SliderRoot = forwardRef>(function SliderRoot({ className, ...props }, ref) {\n return
;\n});\n\nconst SliderTrack = forwardRef>(function SliderTrack(\n { className, ...props },\n ref\n) {\n return
;\n});\n\nconst SliderFill = forwardRef & { type?: 'fill' | 'buffer' }>(function SliderFill(\n { type = 'fill', className, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst SliderThumb = forwardRef & { persistent?: boolean }>(function SliderThumb(\n { persistent, className, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst errorClasses = {\n root: error.root,\n dialog: error.dialog,\n content: error.content,\n title: error.title,\n description: error.description,\n actions: error.actions,\n close: [button.base, button.default].filter(Boolean).join(' '),\n};\n\nfunction PlayLabel(): ReactNode {\n const paused = usePlayer((s) => Boolean(s.paused));\n const ended = usePlayer((s) => Boolean(s.ended));\n if (ended) return <>Replay;\n return paused ? <>Play : <>Pause;\n}\n\nfunction CaptionsLabel(): ReactNode {\n const active = usePlayer((s) => Boolean(s.subtitlesShowing));\n return active ? <>Disable captions : <>Enable captions;\n}\n\nfunction PiPLabel(): ReactNode {\n const pip = usePlayer((s) => Boolean(s.pip));\n return pip ? <>Exit picture-in-picture : <>Enter picture-in-picture;\n}\n\nfunction FullscreenLabel(): ReactNode {\n const fullscreen = usePlayer((s) => Boolean(s.fullscreen));\n return fullscreen ? <>Exit fullscreen : <>Enter fullscreen;\n}\n\n/* ------------------------------------------ Skin ------------------------------------------- */\n\nexport function VideoSkinTailwind(props: VideoSkinProps): ReactNode {\n const { children, className, ...rest } = props;\n\n return (\n \n {children}\n\n (\n
\n
\n \n
\n
\n )}\n />\n\n \n\n \n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n Seek backward {SEEK_TIME} seconds\n \n\n \n (\n \n )}\n />\n }\n />\n Seek forward {SEEK_TIME} seconds\n \n\n \n \n }>\n }>\n } />\n } />\n \n } />\n \n \n \n\n \n \n )}\n />\n }\n />\n \n }\n >\n }>\n } />\n \n } />\n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n \n\n {/*
\n
\n \n An example cue\n \n
\n
*/}\n\n
\n \n );\n}\n", - "jsx": "import { selectError } from '@videojs/core/dom';\nimport { forwardRef, useRef } from 'react';\nimport { AlertDialog, Container, usePlayer, BufferingIndicator, CaptionsButton, Controls, FullscreenButton, MuteButton, PiPButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nexport function ErrorDialog({ classes }) {\n const errorState = usePlayer(selectError);\n const lastError = useRef(errorState?.error);\n if (errorState?.error)\n lastError.current = errorState.error;\n if (!errorState)\n return null;\n return ( {\n if (!open)\n errorState.dismissError();\n }}>\n \n
\n
\n Something went wrong.\n \n {lastError.current?.message ?? 'An error occurred while trying to play the video. Please try again.'}\n \n
\n
\n OK\n
\n
\n
\n
);\n}\nconst bufferingIndicator = {\n root: \"absolute inset-0 hidden items-center justify-center pointer-events-none text-white data-visible:flex\",\n container: \"p-1 rounded-full ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/10 backdrop-brightness-90 backdrop-saturate-150 backdrop-blur-3xl ring-white/5 shadow-black/10 after:ring-black/15 [@media(prefers-reduced-transparency:reduce)]:bg-black/70 contrast-more:bg-black/90\",\n};\nconst button = {\n base: \"items-center justify-center shrink-0 border-none cursor-pointer select-none text-center font-medium outline-2 outline-transparent -outline-offset-2 transition-[background-color,color,outline-offset,scale] duration-150 ease-out disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-blue-500 focus-visible:outline-offset-2 data-[availability=unavailable]:hidden\",\n icon: \"grid w-[2.125rem] aspect-square bg-transparent rounded-full text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 active:scale-90\",\n default: \"flex py-2 px-4 bg-white rounded-full text-black\",\n};\nconst controls = \"peer/controls @container/media-controls p-[0.175rem] flex items-center gap-[0.075rem] rounded-full [--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.25))] [--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))] text-shadow-[0_0_1px_var(--media-controls-current-shadow-color)] @2xl/media-root:p-1 @2xl/media-root:gap-0.5 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/10 backdrop-brightness-90 backdrop-saturate-150 backdrop-blur-3xl ring-white/5 shadow-black/10 after:ring-black/15 [@media(prefers-reduced-transparency:reduce)]:bg-black/70 contrast-more:bg-black/90 absolute bottom-3 inset-x-3 text-white z-10 will-change-[scale,transform,filter,opacity] transition-[scale,transform,filter,opacity] ease-out delay-0 duration-100 origin-bottom not-data-visible:pointer-events-none not-data-visible:blur-sm not-data-visible:scale-90 not-data-visible:opacity-0 not-data-visible:delay-500 not-data-visible:duration-300 motion-reduce:not-data-visible:duration-100 motion-reduce:not-data-visible:blur-none motion-reduce:not-data-visible:scale-100\";\nconst error = {\n root: \"peer/error group/error flex absolute inset-0 z-20 items-center justify-center\",\n dialog: \"flex flex-col gap-3 max-w-72 p-3 rounded-[1.75rem] text-white text-sm transition-[opacity,scale,transform] duration-500 delay-100 ease-[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)] 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 motion-reduce:duration-100 motion-reduce:ease-out motion-reduce:delay-0 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/10 backdrop-brightness-90 backdrop-saturate-150 backdrop-blur-3xl ring-white/5 shadow-black/10 after:ring-black/15 [@media(prefers-reduced-transparency:reduce)]:bg-black/70 contrast-more:bg-black/90\",\n content: \"flex flex-col gap-2 px-2 pt-2 pb-1.5\",\n title: \"font-semibold leading-tight\",\n description: \"opacity-70\",\n actions: \"flex gap-2 *:flex-1\",\n};\nconst icon = \"block [grid-area:1/1] size-4.5 shrink-0 drop-shadow-[0_1px_0_var(--media-controls-current-shadow-color,oklch(0_0_0/0.25))] transition-discrete transition-[display,opacity] duration-150 ease-out\";\nconst iconContainer = \"relative\";\nconst iconFlipped = \"[scale:-1_1]\";\nconst iconState = {\n play: {\n button: \"group\",\n restart: \"hidden opacity-0 group-data-ended:block group-data-ended:opacity-100\",\n play: \"hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100\",\n pause: \"hidden opacity-0 group-not-data-paused:group-not-data-ended:block group-not-data-paused:group-not-data-ended:opacity-100\",\n },\n mute: {\n button: \"group\",\n volumeOff: \"hidden opacity-0 group-data-muted:block group-data-muted:opacity-100\",\n 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\",\n 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\",\n },\n fullscreen: {\n button: \"group\",\n enter: \"hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100\",\n exit: \"hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100\",\n },\n captions: {\n button: \"group\",\n off: \"hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100\",\n on: \"hidden opacity-0 group-data-active:block group-data-active:opacity-100\",\n },\n};\nconst 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 to-transparent backdrop-blur-none backdrop-saturate-120 backdrop-brightness-90 transition-[opacity,backdrop-filter] ease-out duration-300 delay-500 peer-data-visible/controls:opacity-100 peer-data-visible/controls:duration-150 peer-data-visible/controls:delay-0 peer-data-open/error:opacity-100 peer-data-open/error:duration-150 peer-data-open/error:delay-0 peer-data-open/error:backdrop-blur motion-reduce:duration-100\";\nconst playbackRate = {\n button: \"after:content-[attr(data-rate)_'×'] after:w-[4ch] after:tabular-nums\",\n};\nconst popup = {\n base: \"m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-200 data-starting-style:opacity-0 data-starting-style:scale-0 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-0 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 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/10 backdrop-brightness-90 backdrop-saturate-150 backdrop-blur-3xl ring-white/5 shadow-black/10 after:ring-black/15 [@media(prefers-reduced-transparency:reduce)]:bg-black/70 contrast-more:bg-black/90\",\n popover: \"[--media-popover-side-offset:0.5rem]\",\n tooltip: \"py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap [--media-tooltip-side-offset:0.5rem]\",\n volume: \"py-2.5 px-1 rounded-full\",\n};\nconst root = () => \"block relative isolate @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 **:box-border **:m-0 [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] [&:fullscreen]:rounded-none bg-black 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/10 [&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&>img]:absolute [&>img]:inset-0 [&>img]:w-full [&>img]:h-full [&>img]:rounded-[inherit] [&>img]:object-cover [&>img]:pointer-events-none [&>img]:transition-opacity [&>img]:duration-250 [&>img:not([data-visible])]:opacity-0 [--media-caption-track-delay:600ms] [--media-caption-track-y:-0.5rem] has-[[data-controls][data-visible]]:[--media-caption-track-delay:25ms] has-[[data-controls][data-visible]]:[--media-caption-track-y:-3.5rem] [&_video::-webkit-media-text-track-container]:transition-transform [&_video::-webkit-media-text-track-container]:duration-150 [&_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] motion-reduce:[&_video::-webkit-media-text-track-container]:duration-50 [&:fullscreen]:rounded-none\";\nconst seek = {\n button: \"@max-md/media-controls:hidden\",\n label: \"text-[0.75em] font-[480] tabular-nums\",\n labelForward: \"absolute -right-px -bottom-0.75\",\n labelBackward: \"absolute -left-px -bottom-0.75\",\n};\nconst slider = {\n root: \"group/slider relative flex flex-1 items-center justify-center rounded-full outline-none data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5 data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-20\",\n 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 shadow-[0_0_0_1px_oklch(0_0_0/0.05)]\",\n fill: {\n base: \"absolute rounded-[inherit] pointer-events-none\",\n 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)\",\n 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)\",\n },\n thumb: {\n base: \"z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-controls-current-shadow-color-subtle,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/25 hover:outline-offset-0 focus-visible:outline-current/25 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))]\",\n persistent: \"size-3\",\n interactive: \"size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3\",\n },\n};\nconst time = {\n group: \"@container/media-time flex items-center flex-1 gap-3 px-2\",\n current: \"hidden @2xs/media-time:block tabular-nums\",\n duration: \"tabular-nums\",\n};\nconst SEEK_TIME = 10;\n/* --------------------------------------- Components ---------------------------------------- */\nconst Button = forwardRef(function Button({ className, variant, ...props }, ref) {\n return ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n Seek backward {SEEK_TIME} seconds\n \n\n \n ()}/>}/>\n Seek forward {SEEK_TIME} seconds\n \n\n \n \n }>\n }>\n }/>\n }/>\n \n }/>\n \n \n \n\n \n )}/>}/>\n \n }>\n }>\n }/>\n \n }/>\n \n \n \n\n \n ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n \n\n {/*
\n
\n \n An example cue\n \n
\n
*/}\n\n
\n );\n}\n" - }, - { - "id": "default-audio-react-tailwind", - "name": "Default Audio (React + Tailwind)", - "platform": "react", - "style": "tailwind", - "tsx": "import { type ComponentProps, forwardRef, type ReactNode, type PropsWithChildren, type CSSProperties } from 'react';\n\nimport { Container, usePlayer, MuteButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\n\nconst button = {\n base: \"items-center justify-center shrink-0 border-none cursor-pointer select-none text-center font-medium outline-2 outline-transparent -outline-offset-2 transition-[background-color,color,outline-offset,scale] duration-150 ease-out disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-blue-500 focus-visible:outline-offset-2 data-[availability=unavailable]:hidden\",\n icon: \"grid w-[2.125rem] aspect-square bg-transparent rounded-full text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 active:scale-90\",\n default: \"flex py-2 px-4 bg-white rounded-full text-black\",\n};\nconst controls = \"peer/controls @container/media-controls p-[0.175rem] flex items-center gap-[0.075rem] rounded-full [--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.25))] [--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))] text-shadow-[0_0_1px_var(--media-controls-current-shadow-color)] @2xl/media-root:p-1 @2xl/media-root:gap-0.5 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/50 dark:bg-black/40 backdrop-brightness-98 backdrop-saturate-120 backdrop-blur ring-white/5 shadow-black/15 after:ring-black/5 dark:text-white\";\nconst icon = \"block [grid-area:1/1] size-4.5 shrink-0 drop-shadow-[0_1px_0_var(--media-controls-current-shadow-color,oklch(0_0_0/0.25))] transition-discrete transition-[display,opacity] duration-150 ease-out\";\nconst iconContainer = \"relative\";\nconst iconFlipped = \"[scale:-1_1]\";\nconst iconState = {\n play: {\n button: \"group\",\n restart: \"hidden opacity-0 group-data-ended:block group-data-ended:opacity-100\",\n play: \"hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100\",\n pause: \"hidden opacity-0 group-not-data-paused:group-not-data-ended:block group-not-data-paused:group-not-data-ended:opacity-100\",\n },\n mute: {\n button: \"group\",\n volumeOff: \"hidden opacity-0 group-data-muted:block group-data-muted:opacity-100\",\n 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\",\n 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\",\n },\n};\nconst playbackRate = {\n button: \"after:content-[attr(data-rate)_'×'] after:w-[4ch] after:tabular-nums\",\n};\nconst popup = {\n base: \"m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-200 data-starting-style:opacity-0 data-starting-style:scale-0 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-0 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 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/50 dark:bg-black/40 backdrop-brightness-98 backdrop-saturate-120 backdrop-blur ring-white/5 shadow-black/15 after:ring-black/5\",\n popover: \"[--media-popover-side-offset:0.5rem]\",\n tooltip: \"py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap [--media-tooltip-side-offset:0.5rem]\",\n volume: \"py-2.5 px-1 rounded-full\",\n};\nconst root = \"block relative isolate @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 **:box-border **:m-0 [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] [&:fullscreen]:rounded-none\";\nconst seek = {\n button: \"@max-md/media-controls:hidden\",\n label: \"text-[0.75em] font-[480] tabular-nums\",\n labelForward: \"absolute -right-px -bottom-0.75\",\n labelBackward: \"absolute -left-px -bottom-0.75\",\n};\nconst slider = {\n root: \"group/slider relative flex flex-1 items-center justify-center rounded-full outline-none data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5 data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-20\",\n 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:shadow-[0_0_0_1px_oklch(0_0_0/0.05)]\",\n fill: {\n base: \"absolute rounded-[inherit] pointer-events-none\",\n 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)\",\n 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)\",\n },\n thumb: {\n base: \"z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-controls-current-shadow-color-subtle,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/25 hover:outline-offset-0 focus-visible:outline-current/25 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))]\",\n persistent: \"size-3\",\n interactive: \"size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3\",\n },\n};\nconst time = {\n group: \"@container/media-time flex items-center flex-1 gap-3 px-2\",\n current: \"hidden @2xs/media-time:block tabular-nums\",\n duration: \"tabular-nums\",\n};\n\ntype AudioSkinProps = PropsWithChildren<{ style?: CSSProperties; className?: string }>;\nconst SEEK_TIME = 10;\n\n/* --------------------------------------- Components ---------------------------------------- */\n\nconst Button = forwardRef & { variant?: 'icon' }>(function Button(\n { className, variant, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst SliderRoot = forwardRef>(function SliderRoot({ className, ...props }, ref) {\n return
;\n});\n\nconst SliderTrack = forwardRef>(function SliderTrack(\n { className, ...props },\n ref\n) {\n return
;\n});\n\nconst SliderFill = forwardRef & { type?: 'fill' | 'buffer' }>(function SliderFill(\n { type = 'fill', className, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst SliderThumb = forwardRef & { persistent?: boolean }>(function SliderThumb(\n { persistent, className, ...props },\n ref\n) {\n return (\n \n );\n});\n\nfunction PlayLabel(): ReactNode {\n const paused = usePlayer((s) => Boolean(s.paused));\n const ended = usePlayer((s) => Boolean(s.ended));\n if (ended) return <>Replay;\n return paused ? <>Play : <>Pause;\n}\n\n/* ------------------------------------------ Skin ------------------------------------------- */\n\nexport function AudioSkinTailwind(props: AudioSkinProps): ReactNode {\n const { children, className, ...rest } = props;\n\n return (\n \n {children}\n\n
\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n Seek backward {SEEK_TIME} seconds\n \n\n \n (\n \n )}\n />\n }\n />\n Seek forward {SEEK_TIME} seconds\n \n\n \n \n }>\n }>\n } />\n } />\n \n } />\n \n \n \n\n \n \n )}\n />\n }\n />\n \n }\n >\n }>\n } />\n \n } />\n \n \n \n
\n
\n );\n}\n", - "jsx": "import { forwardRef } from 'react';\nimport { Container, usePlayer, MuteButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nconst button = {\n base: \"items-center justify-center shrink-0 border-none cursor-pointer select-none text-center font-medium outline-2 outline-transparent -outline-offset-2 transition-[background-color,color,outline-offset,scale] duration-150 ease-out disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale focus-visible:outline-blue-500 focus-visible:outline-offset-2 data-[availability=unavailable]:hidden\",\n icon: \"grid w-[2.125rem] aspect-square bg-transparent rounded-full text-inherit text-shadow-inherit hover:bg-current/10 hover:no-underline focus-visible:bg-current/10 aria-expanded:bg-current/10 active:scale-90\",\n default: \"flex py-2 px-4 bg-white rounded-full text-black\",\n};\nconst controls = \"peer/controls @container/media-controls p-[0.175rem] flex items-center gap-[0.075rem] rounded-full [--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.25))] [--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))] text-shadow-[0_0_1px_var(--media-controls-current-shadow-color)] @2xl/media-root:p-1 @2xl/media-root:gap-0.5 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/50 dark:bg-black/40 backdrop-brightness-98 backdrop-saturate-120 backdrop-blur ring-white/5 shadow-black/15 after:ring-black/5 dark:text-white\";\nconst icon = \"block [grid-area:1/1] size-4.5 shrink-0 drop-shadow-[0_1px_0_var(--media-controls-current-shadow-color,oklch(0_0_0/0.25))] transition-discrete transition-[display,opacity] duration-150 ease-out\";\nconst iconContainer = \"relative\";\nconst iconFlipped = \"[scale:-1_1]\";\nconst iconState = {\n play: {\n button: \"group\",\n restart: \"hidden opacity-0 group-data-ended:block group-data-ended:opacity-100\",\n play: \"hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100\",\n pause: \"hidden opacity-0 group-not-data-paused:group-not-data-ended:block group-not-data-paused:group-not-data-ended:opacity-100\",\n },\n mute: {\n button: \"group\",\n volumeOff: \"hidden opacity-0 group-data-muted:block group-data-muted:opacity-100\",\n 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\",\n 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\",\n },\n};\nconst playbackRate = {\n button: \"after:content-[attr(data-rate)_'×'] after:w-[4ch] after:tabular-nums\",\n};\nconst popup = {\n base: \"m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-200 data-starting-style:opacity-0 data-starting-style:scale-0 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-0 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 ring ring-inset shadow-sm after:absolute after:inset-0 after:ring after:rounded-[inherit] after:pointer-events-none after:z-10 bg-white/50 dark:bg-black/40 backdrop-brightness-98 backdrop-saturate-120 backdrop-blur ring-white/5 shadow-black/15 after:ring-black/5\",\n popover: \"[--media-popover-side-offset:0.5rem]\",\n tooltip: \"py-1 px-2.5 rounded-full text-[0.75rem] whitespace-nowrap [--media-tooltip-side-offset:0.5rem]\",\n volume: \"py-2.5 px-1 rounded-full\",\n};\nconst root = \"block relative isolate @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 **:box-border **:m-0 [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] [&:fullscreen]:rounded-none\";\nconst seek = {\n button: \"@max-md/media-controls:hidden\",\n label: \"text-[0.75em] font-[480] tabular-nums\",\n labelForward: \"absolute -right-px -bottom-0.75\",\n labelBackward: \"absolute -left-px -bottom-0.75\",\n};\nconst slider = {\n root: \"group/slider relative flex flex-1 items-center justify-center rounded-full outline-none data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5 data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-20\",\n 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:shadow-[0_0_0_1px_oklch(0_0_0/0.05)]\",\n fill: {\n base: \"absolute rounded-[inherit] pointer-events-none\",\n 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)\",\n 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)\",\n },\n thumb: {\n base: \"z-10 absolute -translate-x-1/2 -translate-y-1/2 bg-current rounded-full shadow-[0_0_0_1px_var(--media-controls-current-shadow-color-subtle,oklch(0_0_0/0.1)),0_1px_3px_0_oklch(0_0_0/0.15),0_1px_2px_-1px_oklch(0_0_0/0.15)] transition-[opacity,height,width,outline-offset] duration-150 ease-out select-none outline-4 outline-transparent -outline-offset-4 hover:outline-current/25 hover:outline-offset-0 focus-visible:outline-current/25 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))]\",\n persistent: \"size-3\",\n interactive: \"size-2.5 opacity-0 focus-visible:opacity-100 group-hover/slider:opacity-100 group-active/slider:size-3\",\n },\n};\nconst time = {\n group: \"@container/media-time flex items-center flex-1 gap-3 px-2\",\n current: \"hidden @2xs/media-time:block tabular-nums\",\n duration: \"tabular-nums\",\n};\nconst SEEK_TIME = 10;\n/* --------------------------------------- Components ---------------------------------------- */\nconst Button = forwardRef(function Button({ className, variant, ...props }, ref) {\n return ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n Seek backward {SEEK_TIME} seconds\n \n\n \n ()}/>}/>\n Seek forward {SEEK_TIME} seconds\n \n\n \n \n }>\n }>\n }/>\n }/>\n \n }/>\n \n \n \n\n \n )}/>}/>\n \n }>\n }>\n }/>\n \n }/>\n \n \n \n
\n );\n}\n" - }, - { - "id": "minimal-video-react-tailwind", - "name": "Minimal Video (React + Tailwind)", - "platform": "react", - "style": "tailwind", - "tsx": "import { selectError } from '@videojs/core/dom';\nimport { type ComponentProps, forwardRef, type ReactNode, useRef, type PropsWithChildren, type CSSProperties } from 'react';\n\nimport { AlertDialog, Container, usePlayer, BufferingIndicator, CaptionsButton, Controls, FullscreenButton, MuteButton, PiPButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\n\nexport interface ErrorDialogClasses {\n root?: string;\n dialog?: string;\n content?: string;\n title?: string;\n description?: string;\n actions?: string;\n close?: string;\n}\n\nexport function ErrorDialog({ classes }: { classes?: ErrorDialogClasses }): ReactNode {\n const errorState = usePlayer(selectError);\n const lastError = useRef(errorState?.error);\n if (errorState?.error) lastError.current = errorState.error;\n\n if (!errorState) return null;\n\n return (\n {\n if (!open) errorState.dismissError();\n }}\n >\n \n
\n
\n Something went wrong.\n \n {lastError.current?.message ?? 'An error occurred while trying to play the video. Please try again.'}\n \n
\n
\n OK\n
\n
\n
\n \n );\n}\n\n\nconst playbackRate = {\n button: \"after:content-[attr(data-rate)_'×'] after:w-[4ch] after:tabular-nums\",\n};\n\nconst bufferingIndicator = \"absolute inset-0 hidden items-center justify-center pointer-events-none text-white data-visible:flex\";\nconst button = {\n base: \"items-center justify-center shrink-0 border-none cursor-pointer select-none text-center outline-2 outline-transparent -outline-offset-2 font-medium text-shadow-inherit transition-[background-color,color,outline-offset,scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale data-[availability=unavailable]:hidden\",\n icon: \"grid w-[2.375rem] aspect-square bg-transparent rounded-lg text-inherit hover:text-current/80 hover:no-underline focus-visible:text-current/80 aria-expanded:text-current/80 active:scale-90\",\n default: \"flex py-2 px-4 bg-white rounded-lg text-black\",\n};\nconst buttonGroup = \"flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5\";\nconst controls = \"peer/controls @container/media-controls flex items-center [--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.25))] [--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))] text-shadow-[0_0_1px_var(--media-controls-current-shadow-color)] absolute bottom-0 inset-x-0 pt-8 px-1.5 pb-1.5 gap-2 text-white z-10 will-change-[translate,filter,opacity] transition-[translate,filter,opacity] ease-out delay-0 duration-75 not-data-visible:opacity-0 not-data-visible:translate-y-full not-data-visible:blur-sm not-data-visible:pointer-events-none not-data-visible:delay-500 not-data-visible:duration-500 motion-reduce:not-data-visible:duration-100 motion-reduce:not-data-visible:translate-y-0 motion-reduce:not-data-visible:blur-none motion-reduce:not-data-visible:scale-100 @sm/media-root:pt-10 @sm/media-root:px-3 @sm/media-root:pb-3 @sm/media-root:gap-3.5\";\nconst error = {\n root: \"peer/error group/error flex absolute inset-0 z-20 items-center justify-center\",\n dialog: \"flex flex-col gap-3 max-w-64 p-4 text-white text-sm text-shadow-2xs text-shadow-black/50 transition-[opacity,scale,transform] duration-500 delay-100 ease-[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)] 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 motion-reduce:duration-100 motion-reduce:ease-out motion-reduce:delay-0\",\n content: \"flex flex-col gap-2 py-1.5\",\n title: \"font-semibold leading-tight\",\n description: \"opacity-70\",\n actions: \"flex gap-2 *:flex-1\",\n};\nconst icon = \"block [grid-area:1/1] size-4.5 drop-shadow-[0_1px_0_var(--media-controls-current-shadow-color,oklch(0_0_0/0.25))] transition-discrete transition-[display,opacity] duration-150 ease-out\";\nconst iconContainer = \"relative\";\nconst iconFlipped = \"[scale:-1_1]\";\nconst iconState = {\n play: {\n button: \"group\",\n restart: \"hidden opacity-0 group-data-ended:block group-data-ended:opacity-100\",\n play: \"hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100\",\n pause: \"hidden opacity-0 group-not-data-paused:group-not-data-ended:block group-not-data-paused:group-not-data-ended:opacity-100\",\n },\n mute: {\n button: \"group\",\n volumeOff: \"hidden opacity-0 group-data-muted:block group-data-muted:opacity-100\",\n 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\",\n 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\",\n },\n fullscreen: {\n button: \"group\",\n enter: \"hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100\",\n exit: \"hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100\",\n },\n captions: {\n button: \"group\",\n off: \"hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100\",\n on: \"hidden opacity-0 group-data-active:block group-data-active:opacity-100\",\n },\n};\nconst 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-120 backdrop-brightness-90 transition-[opacity,backdrop-filter] ease-out duration-500 delay-500 peer-data-visible/controls:opacity-100 peer-data-visible/controls:duration-150 peer-data-visible/controls:delay-0 peer-data-open/error:opacity-100 peer-data-open/error:duration-150 peer-data-open/error:delay-0 peer-data-open/error:backdrop-blur motion-reduce:duration-100\";\nconst popup = {\n base: \"m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-200 data-starting-style:opacity-0 data-starting-style:scale-0 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-0 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\",\n tooltip: \"px-2 py-1 rounded-sm shadow-md shadow-black/10 bg-white/10 backdrop-blur-3xl backdrop-saturate-150 backdrop-brightness-90 text-[0.75rem] whitespace-nowrap [--media-tooltip-side-offset:0.5rem]\",\n volume: \"[--media-popover-side-offset:0.5rem] p-1 bg-transparent\",\n};\nconst root = () => \"block relative isolate @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 **:box-border **:m-0 [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] bg-black 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] [&>img]:absolute [&>img]:inset-0 [&>img]:w-full [&>img]:h-full [&>img]:rounded-[inherit] [&>img]:object-cover [&>img]:pointer-events-none [&>img]:transition-opacity [&>img]:duration-250 [&>img:not([data-visible])]:opacity-0 [--media-caption-track-delay:600ms] [--media-caption-track-y:-0.5rem] has-[[data-controls][data-visible]]:[--media-caption-track-delay:25ms] has-[[data-controls][data-visible]]:[--media-caption-track-y:-3.5rem] [&_video::-webkit-media-text-track-container]:transition-transform [&_video::-webkit-media-text-track-container]:duration-150 [&_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] motion-reduce:[&_video::-webkit-media-text-track-container]:duration-50 [&:fullscreen]:rounded-none\";\nconst seek = {\n button: \"@max-md/media-controls:hidden\",\n label: \"text-[0.75em] font-[480] tabular-nums\",\n labelForward: \"absolute -right-px -bottom-0.75\",\n labelBackward: \"absolute -left-px -bottom-0.75\",\n};\nconst slider = {\n root: \"group/slider relative flex flex-1 items-center justify-center rounded-full outline-none data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5 data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-[4.5rem]\",\n 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 shadow-[0_0_0_1px_oklch(0_0_0/0.05)]\",\n fill: {\n base: \"absolute rounded-[inherit] pointer-events-none\",\n 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)\",\n 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)\",\n },\n thumb: {\n 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-controls-current-shadow-color-subtle,oklch(0_0_0/0.1)),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))]\",\n 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\",\n },\n};\nconst time = {\n group: \"flex items-center gap-1\",\n current: \"hidden tabular-nums @md/media-controls:inline\",\n separator: \"hidden @md/media-controls:inline @md/media-controls:text-white/50\",\n duration: \"tabular-nums @md/media-controls:text-current/60\",\n controls: \"flex flex-row-reverse items-center flex-1 gap-3 @md/media-controls:flex-row\",\n};\n\ntype MinimalVideoSkinProps = PropsWithChildren<{ style?: CSSProperties; className?: string }>;\nconst SEEK_TIME = 10;\n\n/* --------------------------------------- Components ---------------------------------------- */\n\nconst Button = forwardRef & { variant?: 'icon' }>(function Button(\n { className, variant, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst SliderRoot = forwardRef>(function SliderRoot({ className, ...props }, ref) {\n return
;\n});\n\nconst SliderTrack = forwardRef>(function SliderTrack(\n { className, ...props },\n ref\n) {\n return
;\n});\n\nconst SliderFill = forwardRef & { type?: 'fill' | 'buffer' }>(function SliderFill(\n { type = 'fill', className, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst SliderThumb = forwardRef & { persistent?: boolean }>(function SliderThumb(\n { persistent, className, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst errorClasses = {\n root: error.root,\n dialog: error.dialog,\n content: error.content,\n title: error.title,\n actions: error.actions,\n close: [button.base, button.default].filter(Boolean).join(' '),\n};\n\nfunction PlayLabel(): ReactNode {\n const paused = usePlayer((s) => Boolean(s.paused));\n const ended = usePlayer((s) => Boolean(s.ended));\n if (ended) return <>Replay;\n return paused ? <>Play : <>Pause;\n}\n\nfunction CaptionsLabel(): ReactNode {\n const active = usePlayer((s) => Boolean(s.subtitlesShowing));\n return active ? <>Disable captions : <>Enable captions;\n}\n\nfunction PiPLabel(): ReactNode {\n const pip = usePlayer((s) => Boolean(s.pip));\n return pip ? <>Exit picture-in-picture : <>Enter picture-in-picture;\n}\n\nfunction FullscreenLabel(): ReactNode {\n const fullscreen = usePlayer((s) => Boolean(s.fullscreen));\n return fullscreen ? <>Exit fullscreen : <>Enter fullscreen;\n}\n\n/* ------------------------------------------ Skin ------------------------------------------- */\n\nexport function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNode {\n const { children, className, ...rest } = props;\n\n return (\n \n {children}\n\n (\n
\n \n
\n )}\n />\n\n \n\n \n \n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n Seek backward {SEEK_TIME} seconds\n \n\n \n (\n \n )}\n />\n }\n />\n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n\n }>\n }>\n } />\n } />\n \n } />\n \n \n\n \n \n \n )}\n />\n }\n />\n \n }\n >\n }>\n } />\n \n } />\n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n \n \n \n \n \n \n\n {/*
\n
\n \n An example cue\n \n
\n
*/}\n\n
\n \n );\n}\n", - "jsx": "import { selectError } from '@videojs/core/dom';\nimport { forwardRef, useRef } from 'react';\nimport { AlertDialog, Container, usePlayer, BufferingIndicator, CaptionsButton, Controls, FullscreenButton, MuteButton, PiPButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nexport function ErrorDialog({ classes }) {\n const errorState = usePlayer(selectError);\n const lastError = useRef(errorState?.error);\n if (errorState?.error)\n lastError.current = errorState.error;\n if (!errorState)\n return null;\n return ( {\n if (!open)\n errorState.dismissError();\n }}>\n \n
\n
\n Something went wrong.\n \n {lastError.current?.message ?? 'An error occurred while trying to play the video. Please try again.'}\n \n
\n
\n OK\n
\n
\n
\n
);\n}\nconst playbackRate = {\n button: \"after:content-[attr(data-rate)_'×'] after:w-[4ch] after:tabular-nums\",\n};\nconst bufferingIndicator = \"absolute inset-0 hidden items-center justify-center pointer-events-none text-white data-visible:flex\";\nconst button = {\n base: \"items-center justify-center shrink-0 border-none cursor-pointer select-none text-center outline-2 outline-transparent -outline-offset-2 font-medium text-shadow-inherit transition-[background-color,color,outline-offset,scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale data-[availability=unavailable]:hidden\",\n icon: \"grid w-[2.375rem] aspect-square bg-transparent rounded-lg text-inherit hover:text-current/80 hover:no-underline focus-visible:text-current/80 aria-expanded:text-current/80 active:scale-90\",\n default: \"flex py-2 px-4 bg-white rounded-lg text-black\",\n};\nconst buttonGroup = \"flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5\";\nconst controls = \"peer/controls @container/media-controls flex items-center [--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.25))] [--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))] text-shadow-[0_0_1px_var(--media-controls-current-shadow-color)] absolute bottom-0 inset-x-0 pt-8 px-1.5 pb-1.5 gap-2 text-white z-10 will-change-[translate,filter,opacity] transition-[translate,filter,opacity] ease-out delay-0 duration-75 not-data-visible:opacity-0 not-data-visible:translate-y-full not-data-visible:blur-sm not-data-visible:pointer-events-none not-data-visible:delay-500 not-data-visible:duration-500 motion-reduce:not-data-visible:duration-100 motion-reduce:not-data-visible:translate-y-0 motion-reduce:not-data-visible:blur-none motion-reduce:not-data-visible:scale-100 @sm/media-root:pt-10 @sm/media-root:px-3 @sm/media-root:pb-3 @sm/media-root:gap-3.5\";\nconst error = {\n root: \"peer/error group/error flex absolute inset-0 z-20 items-center justify-center\",\n dialog: \"flex flex-col gap-3 max-w-64 p-4 text-white text-sm text-shadow-2xs text-shadow-black/50 transition-[opacity,scale,transform] duration-500 delay-100 ease-[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)] 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 motion-reduce:duration-100 motion-reduce:ease-out motion-reduce:delay-0\",\n content: \"flex flex-col gap-2 py-1.5\",\n title: \"font-semibold leading-tight\",\n description: \"opacity-70\",\n actions: \"flex gap-2 *:flex-1\",\n};\nconst icon = \"block [grid-area:1/1] size-4.5 drop-shadow-[0_1px_0_var(--media-controls-current-shadow-color,oklch(0_0_0/0.25))] transition-discrete transition-[display,opacity] duration-150 ease-out\";\nconst iconContainer = \"relative\";\nconst iconFlipped = \"[scale:-1_1]\";\nconst iconState = {\n play: {\n button: \"group\",\n restart: \"hidden opacity-0 group-data-ended:block group-data-ended:opacity-100\",\n play: \"hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100\",\n pause: \"hidden opacity-0 group-not-data-paused:group-not-data-ended:block group-not-data-paused:group-not-data-ended:opacity-100\",\n },\n mute: {\n button: \"group\",\n volumeOff: \"hidden opacity-0 group-data-muted:block group-data-muted:opacity-100\",\n 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\",\n 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\",\n },\n fullscreen: {\n button: \"group\",\n enter: \"hidden opacity-0 group-not-data-fullscreen:block group-not-data-fullscreen:opacity-100\",\n exit: \"hidden opacity-0 group-data-fullscreen:block group-data-fullscreen:opacity-100\",\n },\n captions: {\n button: \"group\",\n off: \"hidden opacity-0 group-not-data-active:block group-not-data-active:opacity-100\",\n on: \"hidden opacity-0 group-data-active:block group-data-active:opacity-100\",\n },\n};\nconst 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-120 backdrop-brightness-90 transition-[opacity,backdrop-filter] ease-out duration-500 delay-500 peer-data-visible/controls:opacity-100 peer-data-visible/controls:duration-150 peer-data-visible/controls:delay-0 peer-data-open/error:opacity-100 peer-data-open/error:duration-150 peer-data-open/error:delay-0 peer-data-open/error:backdrop-blur motion-reduce:duration-100\";\nconst popup = {\n base: \"m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-200 data-starting-style:opacity-0 data-starting-style:scale-0 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-0 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\",\n tooltip: \"px-2 py-1 rounded-sm shadow-md shadow-black/10 bg-white/10 backdrop-blur-3xl backdrop-saturate-150 backdrop-brightness-90 text-[0.75rem] whitespace-nowrap [--media-tooltip-side-offset:0.5rem]\",\n volume: \"[--media-popover-side-offset:0.5rem] p-1 bg-transparent\",\n};\nconst root = () => \"block relative isolate @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 **:box-border **:m-0 [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] bg-black 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] [&>img]:absolute [&>img]:inset-0 [&>img]:w-full [&>img]:h-full [&>img]:rounded-[inherit] [&>img]:object-cover [&>img]:pointer-events-none [&>img]:transition-opacity [&>img]:duration-250 [&>img:not([data-visible])]:opacity-0 [--media-caption-track-delay:600ms] [--media-caption-track-y:-0.5rem] has-[[data-controls][data-visible]]:[--media-caption-track-delay:25ms] has-[[data-controls][data-visible]]:[--media-caption-track-y:-3.5rem] [&_video::-webkit-media-text-track-container]:transition-transform [&_video::-webkit-media-text-track-container]:duration-150 [&_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] motion-reduce:[&_video::-webkit-media-text-track-container]:duration-50 [&:fullscreen]:rounded-none\";\nconst seek = {\n button: \"@max-md/media-controls:hidden\",\n label: \"text-[0.75em] font-[480] tabular-nums\",\n labelForward: \"absolute -right-px -bottom-0.75\",\n labelBackward: \"absolute -left-px -bottom-0.75\",\n};\nconst slider = {\n root: \"group/slider relative flex flex-1 items-center justify-center rounded-full outline-none data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5 data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-[4.5rem]\",\n 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 shadow-[0_0_0_1px_oklch(0_0_0/0.05)]\",\n fill: {\n base: \"absolute rounded-[inherit] pointer-events-none\",\n 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)\",\n 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)\",\n },\n thumb: {\n 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-controls-current-shadow-color-subtle,oklch(0_0_0/0.1)),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))]\",\n 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\",\n },\n};\nconst time = {\n group: \"flex items-center gap-1\",\n current: \"hidden tabular-nums @md/media-controls:inline\",\n separator: \"hidden @md/media-controls:inline @md/media-controls:text-white/50\",\n duration: \"tabular-nums @md/media-controls:text-current/60\",\n controls: \"flex flex-row-reverse items-center flex-1 gap-3 @md/media-controls:flex-row\",\n};\nconst SEEK_TIME = 10;\n/* --------------------------------------- Components ---------------------------------------- */\nconst Button = forwardRef(function Button({ className, variant, ...props }, ref) {\n return ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n Seek backward {SEEK_TIME} seconds\n \n\n \n ()}/>}/>\n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n\n }>\n }>\n }/>\n }/>\n \n }/>\n \n \n\n \n \n )}/>}/>\n \n }>\n }>\n }/>\n \n }/>\n \n \n \n\n \n ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n \n \n \n \n \n \n\n {/*
\n
\n \n An example cue\n \n
\n
*/}\n\n
\n );\n}\n" - }, - { - "id": "minimal-audio-react-tailwind", - "name": "Minimal Audio (React + Tailwind)", - "platform": "react", - "style": "tailwind", - "tsx": "import { type ComponentProps, forwardRef, type ReactNode, type PropsWithChildren, type CSSProperties } from 'react';\n\nimport { Container, usePlayer, MuteButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\n\nconst playbackRate = {\n button: \"after:content-[attr(data-rate)_'×'] after:w-[4ch] after:tabular-nums\",\n};\n\nconst button = {\n base: \"items-center justify-center shrink-0 border-none cursor-pointer select-none text-center outline-2 outline-transparent -outline-offset-2 font-medium text-shadow-inherit transition-[background-color,color,outline-offset,scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale data-[availability=unavailable]:hidden\",\n icon: \"grid w-[2.375rem] aspect-square bg-transparent rounded-lg text-inherit hover:text-current/80 hover:no-underline focus-visible:text-current/80 aria-expanded:text-current/80 active:scale-90\",\n default: \"flex py-2 px-4 bg-white rounded-lg text-black\",\n};\nconst buttonGroup = \"flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5\";\nconst controls = \"peer/controls @container/media-controls flex items-center [--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.25))] [--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))] text-shadow-[0_0_1px_var(--media-controls-current-shadow-color)] gap-2 p-1.5 rounded-(--media-border-radius,0.75rem) bg-(--media-controls-background-color) text-(--media-controls-text-color) backdrop-blur backdrop-brightness-[0.98] backdrop-saturate-[1.2] ring-1 ring-(color:--media-controls-border-color)\";\nconst icon = \"block [grid-area:1/1] size-4.5 drop-shadow-[0_1px_0_var(--media-controls-current-shadow-color,oklch(0_0_0/0.25))] transition-discrete transition-[display,opacity] duration-150 ease-out\";\nconst iconContainer = \"relative\";\nconst iconFlipped = \"[scale:-1_1]\";\nconst iconState = {\n play: {\n button: \"group\",\n restart: \"hidden opacity-0 group-data-ended:block group-data-ended:opacity-100\",\n play: \"hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100\",\n pause: \"hidden opacity-0 group-not-data-paused:group-not-data-ended:block group-not-data-paused:group-not-data-ended:opacity-100\",\n },\n mute: {\n button: \"group\",\n volumeOff: \"hidden opacity-0 group-data-muted:block group-data-muted:opacity-100\",\n 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\",\n 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\",\n },\n};\nconst popup = {\n base: \"m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-200 data-starting-style:opacity-0 data-starting-style:scale-0 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-0 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\",\n tooltip: \"px-2 py-1 rounded-sm shadow-md shadow-black/10 bg-white/10 backdrop-blur-3xl backdrop-saturate-150 backdrop-brightness-90 text-[0.75rem] whitespace-nowrap [--media-tooltip-side-offset:0.5rem]\",\n volume: \"py-2 pr-0 pl-16 bg-transparent bg-gradient-to-l from-(--media-controls-background-color) from-80% to-transparent [--media-popover-side-offset:0.75rem]\",\n};\nconst root = \"block relative isolate @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 **:box-border **:m-0 [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] [--media-controls-background-color:oklch(1_0_0)] [--media-controls-border-color:oklch(0_0_0/0.1)] [--media-controls-text-color:oklch(0_0_0)] 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:oklch(1_0_0)]\";\nconst seek = {\n button: \"@max-md/media-controls:hidden\",\n label: \"text-[0.75em] font-[480] tabular-nums\",\n labelForward: \"absolute -right-px -bottom-0.75\",\n labelBackward: \"absolute -left-px -bottom-0.75\",\n};\nconst slider = {\n root: \"group/slider relative flex flex-1 items-center justify-center rounded-full outline-none data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5 data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-[4.5rem]\",\n 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\",\n fill: {\n base: \"absolute rounded-[inherit] pointer-events-none\",\n 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)\",\n 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)\",\n },\n thumb: {\n 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-controls-current-shadow-color-subtle,oklch(0_0_0/0.1)),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))]\",\n 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\",\n },\n};\nconst time = {\n group: \"flex items-center gap-1\",\n current: \"hidden tabular-nums @md/media-controls:inline\",\n separator: \"hidden @md/media-controls:inline @md/media-controls:text-white/50\",\n duration: \"tabular-nums @md/media-controls:text-current/60\",\n controls: \"flex flex-row-reverse items-center flex-1 gap-3 @md/media-controls:flex-row\",\n};\n\ntype MinimalAudioSkinProps = PropsWithChildren<{ style?: CSSProperties; className?: string }>;\nconst SEEK_TIME = 10;\n\n/* --------------------------------------- Components ---------------------------------------- */\n\nconst Button = forwardRef & { variant?: 'icon' }>(function Button(\n { className, variant, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst SliderRoot = forwardRef>(function SliderRoot({ className, ...props }, ref) {\n return
;\n});\n\nconst SliderTrack = forwardRef>(function SliderTrack(\n { className, ...props },\n ref\n) {\n return
;\n});\n\nconst SliderFill = forwardRef & { type?: 'fill' | 'buffer' }>(function SliderFill(\n { type = 'fill', className, ...props },\n ref\n) {\n return (\n \n );\n});\n\nconst SliderThumb = forwardRef & { persistent?: boolean }>(function SliderThumb(\n { persistent, className, ...props },\n ref\n) {\n return (\n \n );\n});\n\nfunction PlayLabel(): ReactNode {\n const paused = usePlayer((s) => Boolean(s.paused));\n const ended = usePlayer((s) => Boolean(s.ended));\n if (ended) return <>Replay;\n return paused ? <>Play : <>Pause;\n}\n\n/* ------------------------------------------ Skin ------------------------------------------- */\n\nexport function MinimalAudioSkinTailwind(props: MinimalAudioSkinProps): ReactNode {\n const { children, className, ...rest } = props;\n\n return (\n \n {children}\n\n
\n \n \n (\n \n )}\n />\n }\n />\n \n \n \n \n\n \n (\n \n )}\n />\n }\n />\n Seek backward {SEEK_TIME} seconds\n \n\n \n (\n \n )}\n />\n }\n />\n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n\n }>\n }>\n } />\n } />\n \n } />\n \n \n\n \n \n \n )}\n />\n }\n />\n \n }\n >\n }>\n } />\n \n } />\n \n \n \n \n
\n
\n );\n}\n", - "jsx": "import { forwardRef } from 'react';\nimport { Container, usePlayer, MuteButton, PlayButton, PlaybackRateButton, Popover, SeekButton, Time, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react';\nconst playbackRate = {\n button: \"after:content-[attr(data-rate)_'×'] after:w-[4ch] after:tabular-nums\",\n};\nconst button = {\n base: \"items-center justify-center shrink-0 border-none cursor-pointer select-none text-center outline-2 outline-transparent -outline-offset-2 font-medium text-shadow-inherit transition-[background-color,color,outline-offset,scale] duration-150 ease-out focus-visible:outline-current focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale data-[availability=unavailable]:hidden\",\n icon: \"grid w-[2.375rem] aspect-square bg-transparent rounded-lg text-inherit hover:text-current/80 hover:no-underline focus-visible:text-current/80 aria-expanded:text-current/80 active:scale-90\",\n default: \"flex py-2 px-4 bg-white rounded-lg text-black\",\n};\nconst buttonGroup = \"flex items-center gap-[0.075rem] @2xl/media-root:gap-0.5\";\nconst controls = \"peer/controls @container/media-controls flex items-center [--media-controls-current-shadow-color:oklch(from_currentColor_0_0_0/clamp(0,calc((l-0.5)*0.5),0.25))] [--media-controls-current-shadow-color-subtle:oklch(from_var(--media-controls-current-shadow-color)_l_c_h/calc(alpha*0.4))] text-shadow-[0_0_1px_var(--media-controls-current-shadow-color)] gap-2 p-1.5 rounded-(--media-border-radius,0.75rem) bg-(--media-controls-background-color) text-(--media-controls-text-color) backdrop-blur backdrop-brightness-[0.98] backdrop-saturate-[1.2] ring-1 ring-(color:--media-controls-border-color)\";\nconst icon = \"block [grid-area:1/1] size-4.5 drop-shadow-[0_1px_0_var(--media-controls-current-shadow-color,oklch(0_0_0/0.25))] transition-discrete transition-[display,opacity] duration-150 ease-out\";\nconst iconContainer = \"relative\";\nconst iconFlipped = \"[scale:-1_1]\";\nconst iconState = {\n play: {\n button: \"group\",\n restart: \"hidden opacity-0 group-data-ended:block group-data-ended:opacity-100\",\n play: \"hidden opacity-0 group-not-data-ended:group-data-paused:block group-not-data-ended:group-data-paused:opacity-100\",\n pause: \"hidden opacity-0 group-not-data-paused:group-not-data-ended:block group-not-data-paused:group-not-data-ended:opacity-100\",\n },\n mute: {\n button: \"group\",\n volumeOff: \"hidden opacity-0 group-data-muted:block group-data-muted:opacity-100\",\n 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\",\n 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\",\n },\n};\nconst popup = {\n base: \"m-0 border-0 text-inherit overflow-visible transition-[transform,scale,opacity,filter] duration-200 data-starting-style:opacity-0 data-starting-style:scale-0 data-starting-style:blur-sm data-ending-style:opacity-0 data-ending-style:scale-0 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\",\n tooltip: \"px-2 py-1 rounded-sm shadow-md shadow-black/10 bg-white/10 backdrop-blur-3xl backdrop-saturate-150 backdrop-brightness-90 text-[0.75rem] whitespace-nowrap [--media-tooltip-side-offset:0.5rem]\",\n volume: \"py-2 pr-0 pl-16 bg-transparent bg-gradient-to-l from-(--media-controls-background-color) from-80% to-transparent [--media-popover-side-offset:0.75rem]\",\n};\nconst root = \"block relative isolate @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 **:box-border **:m-0 [&_button]:font-[inherit] motion-safe:[interpolate-size:allow-keywords] [--media-controls-background-color:oklch(1_0_0)] [--media-controls-border-color:oklch(0_0_0/0.1)] [--media-controls-text-color:oklch(0_0_0)] 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:oklch(1_0_0)]\";\nconst seek = {\n button: \"@max-md/media-controls:hidden\",\n label: \"text-[0.75em] font-[480] tabular-nums\",\n labelForward: \"absolute -right-px -bottom-0.75\",\n labelBackward: \"absolute -left-px -bottom-0.75\",\n};\nconst slider = {\n root: \"group/slider relative flex flex-1 items-center justify-center rounded-full outline-none data-[orientation=horizontal]:min-w-20 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:h-5 data-[orientation=vertical]:w-5 data-[orientation=vertical]:h-[4.5rem]\",\n 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\",\n fill: {\n base: \"absolute rounded-[inherit] pointer-events-none\",\n 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)\",\n 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)\",\n },\n thumb: {\n 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-controls-current-shadow-color-subtle,oklch(0_0_0/0.1)),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))]\",\n 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\",\n },\n};\nconst time = {\n group: \"flex items-center gap-1\",\n current: \"hidden tabular-nums @md/media-controls:inline\",\n separator: \"hidden @md/media-controls:inline @md/media-controls:text-white/50\",\n duration: \"tabular-nums @md/media-controls:text-current/60\",\n controls: \"flex flex-row-reverse items-center flex-1 gap-3 @md/media-controls:flex-row\",\n};\nconst SEEK_TIME = 10;\n/* --------------------------------------- Components ---------------------------------------- */\nconst Button = forwardRef(function Button({ className, variant, ...props }, ref) {\n return ()}/>}/>\n \n \n \n \n\n \n ()}/>}/>\n Seek backward {SEEK_TIME} seconds\n \n\n \n ()}/>}/>\n Seek forward {SEEK_TIME} seconds\n \n \n\n \n \n \n \n \n \n\n }>\n }>\n }/>\n }/>\n \n }/>\n \n \n\n \n \n )}/>}/>\n \n }>\n }>\n }/>\n \n }/>\n \n \n \n \n
\n );\n}\n" - } -] diff --git a/site/src/layouts/Base.astro b/site/src/layouts/Base.astro index 8a730fe1..ff3d50bd 100644 --- a/site/src/layouts/Base.astro +++ b/site/src/layouts/Base.astro @@ -2,7 +2,7 @@ import { Font } from 'astro:assets'; import type { ImageMetadata } from 'astro'; -import { SEO_SUFFIX, SITE_TITLE } from '@/consts'; +import { PRODUCTION_URL, SEO_SUFFIX, SITE_TITLE } from '@/consts'; import '@/styles/globals.css'; @@ -14,14 +14,22 @@ import ThemeInit from '@/components/ThemeInit.astro'; interface Props { title: string | string[]; description: string; - image?: ImageMetadata; + image?: ImageMetadata | string; + twitterImage?: ImageMetadata | string; canonical?: string; suffix?: string; } const { canonical, suffix } = Astro.props; -const canonicalURL = canonical || new URL(Astro.url.pathname, Astro.site); +const canonicalURL = canonical || new URL(Astro.url.pathname, PRODUCTION_URL); -const { title, description, image } = Astro.props; +const { title, description, image, twitterImage } = Astro.props; + +function resolveImageUrl(img: ImageMetadata | string): string { + if (typeof img === 'string') return img; + return new URL(img.src, Astro.url).toString(); +} + +const resolvedTwitterImage = twitterImage ?? image; const seoSuffix = suffix ?? SEO_SUFFIX; const pageTitle = Array.isArray(title) ? title.join(' | ') : title; @@ -109,25 +117,27 @@ const fullTitle = {/* Open Graph / Facebook */} - + - { - image && ( - - ) - } + {image && } {/* Twitter */} - + { - image && ( + resolvedTwitterImage && ( ) } diff --git a/site/src/layouts/Blog.astro b/site/src/layouts/Blog.astro index d113f106..433475eb 100644 --- a/site/src/layouts/Blog.astro +++ b/site/src/layouts/Blog.astro @@ -1,5 +1,6 @@ --- +import type { ImageMetadata } from 'astro'; import Footer from '@/components/Footer.astro'; import FooterEasterEgg from '@/components/FooterEasterEgg.astro'; import NavBar from '@/components/NavBar/NavBar.astro'; @@ -9,12 +10,20 @@ interface Props { title: string | string[]; description: string; canonical?: string; + image?: ImageMetadata | string; + twitterImage?: ImageMetadata | string; } -const { title, description, canonical } = Astro.props; +const { title, description, canonical, image, twitterImage } = Astro.props; --- - +
diff --git a/site/src/pages/blog/[...page].astro b/site/src/pages/blog/[...page].astro index fcaf2a41..428660fa 100644 --- a/site/src/pages/blog/[...page].astro +++ b/site/src/pages/blog/[...page].astro @@ -7,6 +7,7 @@ import BlogPagination from '@/components/blog/BlogPagination.astro'; import BlogPostCard from '@/components/blog/BlogPostCard.astro'; import FooterEasterEgg from '@/components/FooterEasterEgg.astro'; import JsonLd from '@/components/JsonLd.astro'; +import { PRODUCTION_URL } from '@/consts'; import Blog from '@/layouts/Blog.astro'; import { createBlogCollectionSchema } from '@/utils/jsonLd/schemas'; @@ -37,11 +38,11 @@ interface Props { const { page } = Astro.props; // Build JSON-LD schema for CollectionPage -const pageUrl = new URL(Astro.url.pathname, Astro.site).toString(); +const pageUrl = new URL(Astro.url.pathname, PRODUCTION_URL).toString(); const jsonLdSchema = createBlogCollectionSchema({ url: pageUrl, posts: page.data, - siteUrl: Astro.site!.toString(), + siteUrl: PRODUCTION_URL.toString(), }); --- diff --git a/site/src/pages/blog/[...slug].astro b/site/src/pages/blog/[...slug].astro index e668b71f..b140a299 100644 --- a/site/src/pages/blog/[...slug].astro +++ b/site/src/pages/blog/[...slug].astro @@ -5,6 +5,7 @@ import { getCollection, getEntries, render } from 'astro:content'; import FormattedDate from '@/components/FormattedDate.astro'; import JsonLd from '@/components/JsonLd.astro'; import defaultMarkdownComponents from '@/components/typography/defaultMarkdownComponents'; +import { PRODUCTION_URL } from '@/consts'; import Blog from '@/layouts/Blog.astro'; import { createBlogPostingSchema } from '@/utils/jsonLd/schemas'; @@ -23,8 +24,15 @@ const post = Astro.props; const { Content, remarkPluginFrontmatter } = await render(post); const authors = await getEntries(post.data.authors); +// Resolve OG image URL for JSON-LD +const ogImageUrl = post.data.ogImage + ? typeof post.data.ogImage === 'string' + ? post.data.ogImage + : new URL(post.data.ogImage.src, Astro.url).toString() + : undefined; + // Build JSON-LD schema for BlogPosting -const pageUrl = new URL(Astro.url.pathname, Astro.site).toString(); +const pageUrl = new URL(Astro.url.pathname, PRODUCTION_URL).toString(); const jsonLdSchema = createBlogPostingSchema({ title: post.data.title, description: post.data.description, @@ -33,7 +41,8 @@ const jsonLdSchema = createBlogPostingSchema({ updatedDate: post.data.updatedDate, readingTime: remarkPluginFrontmatter.readingTimeMinutes, authors, - siteUrl: Astro.site!.toString(), + siteUrl: PRODUCTION_URL.toString(), + image: ogImageUrl, }); --- @@ -41,6 +50,8 @@ const jsonLdSchema = createBlogPostingSchema({ title={[post.data.title, "Blog"]} description={post.data.description} canonical={post.data.canonical} + image={post.data.ogImage} + twitterImage={post.data.twitterImage} >
[d.id, getDocTitle(d, framework)])) const sections = getSectionsForGuide(slug, filterSidebar(framework)); // Build JSON-LD schema for TechArticle -const pageUrl = new URL(Astro.url.pathname, Astro.site).toString(); +const pageUrl = new URL(Astro.url.pathname, PRODUCTION_URL).toString(); const jsonLdSchema = createTechArticleSchema({ title: getDocTitle(doc, framework), description: doc.data.description, diff --git a/site/src/utils/jsonLd/schemas.ts b/site/src/utils/jsonLd/schemas.ts index 6a958081..b370d3fb 100644 --- a/site/src/utils/jsonLd/schemas.ts +++ b/site/src/utils/jsonLd/schemas.ts @@ -63,6 +63,7 @@ export function createBlogPostingSchema(params: { readingTime?: number; authors: CollectionEntry<'authors'>[]; siteUrl: string; + image?: string; }): WithContext { return { '@context': 'https://schema.org', @@ -70,6 +71,7 @@ export function createBlogPostingSchema(params: { headline: params.title, description: params.description, url: params.url, + ...(params.image && { image: params.image }), datePublished: params.pubDate.toISOString(), ...(params.updatedDate && { dateModified: params.updatedDate.toISOString() }), ...(params.wordCount && { wordCount: params.wordCount }), diff --git a/turbo.json b/turbo.json index b36f7d6c..b63b43fc 100644 --- a/turbo.json +++ b/turbo.json @@ -2,6 +2,7 @@ "$schema": "https://turbo.build/schema.json", "ui": "stream", "concurrency": "20", + "globalEnv": ["DEPLOY_PRIME_URL"], "tasks": { "build": { "dependsOn": ["^build"],