From 156986abb99cc6b6bf0a5b1a44a8867aeefa0ae0 Mon Sep 17 00:00:00 2001 From: Darius Cepulis Date: Tue, 10 Mar 2026 19:11:06 -0500 Subject: [PATCH] feat(site): add optional OG image support to blog posts (#878) Co-authored-by: Claude Opus 4.6 --- site/CLAUDE.md | 7 + site/astro.config.mjs | 9 +- .../og.png | Bin 0 -> 33795 bytes .../2099-12-31-blog-component-test/og.png | Bin 0 -> 33795 bytes site/src/consts.ts | 4 + site/src/content.config.ts | 4 +- ...-10-videojs-v10-beta-hello-world-again.mdx | 1 + .../blog/2099-12-31-blog-component-test.mdx | 1 + site/src/content/ejected-skins.json | 130 ------------------ site/src/layouts/Base.astro | 36 +++-- site/src/layouts/Blog.astro | 13 +- site/src/pages/blog/[...page].astro | 5 +- site/src/pages/blog/[...slug].astro | 15 +- site/src/pages/blog/authors/[author].astro | 5 +- .../framework/[framework]/[...slug].astro | 3 +- site/src/utils/jsonLd/schemas.ts | 2 + turbo.json | 1 + 17 files changed, 82 insertions(+), 154 deletions(-) create mode 100644 site/src/assets/blog/2026-03-10-videojs-v10-beta-hello-world-again/og.png create mode 100644 site/src/assets/blog/2099-12-31-blog-component-test/og.png delete mode 100644 site/src/content/ejected-skins.json 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 0000000000000000000000000000000000000000..eed69da0b80886a75818a568dc64d51740747066 GIT binary patch literal 33795 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eed69da0b80886a75818a568dc64d51740747066 GIT binary patch literal 33795 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 literal 0 HcmV?d00001 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"],