From ea92478244be7de3c27b41bfeb1daba7a42688da Mon Sep 17 00:00:00 2001 From: Darius Cepulis Date: Thu, 19 Feb 2026 14:28:57 -0600 Subject: [PATCH 1/2] feat(site): replace home page preview player with real @videojs/react Replace `@videojs/react-preview` and `@videojs/html-preview` with the real `@videojs/react` player on the home page. Update HeroVideo to use `createPlayer`, `VideoSkin`/`MinimalVideoSkin`, and `Poster`. Update BaseDemo code snippets to show the real API for both React and HTML (CDN). Remove preview package dependencies from the site. Closes #578 Co-Authored-By: Claude Opus 4.6 --- pnpm-lock.yaml | 8 +- site/astro.config.mjs | 2 +- site/package.json | 2 - site/src/components/HomePageDemo/Base.tsx | 47 +- site/src/components/HomePageDemo/Eject.tsx | 17 +- site/src/components/home/HeroVideo.tsx | 23 +- .../content/docs/how-to/customize-skins.mdx | 96 +- site/src/utils/ejectCodeGenerator.ts | 1916 ----------------- 8 files changed, 44 insertions(+), 2067 deletions(-) delete mode 100644 site/src/utils/ejectCodeGenerator.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce79718c9..62b860ab8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -450,15 +450,9 @@ importers: '@videojs/html': specifier: workspace:* version: link:../packages/html - '@videojs/html-preview': - specifier: workspace:* - version: link:../packages/__tech-preview__/html '@videojs/react': specifier: workspace:* version: link:../packages/react - '@videojs/react-preview': - specifier: workspace:* - version: link:../packages/__tech-preview__/react astro: specifier: ^5.14.4 version: 5.16.5(@netlify/blobs@10.5.0)(@types/node@22.19.3)(@vercel/functions@2.2.13)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) @@ -10095,7 +10089,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.3)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.3)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) '@vitest/utils@3.2.4': dependencies: diff --git a/site/astro.config.mjs b/site/astro.config.mjs index 0b3a8f4d2..9e9e11f5a 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -96,7 +96,7 @@ export default defineConfig({ vite: { plugins: [tailwindcss()], optimizeDeps: { - exclude: ['@videojs/react-preview', '@videojs/react', '@videojs/html'], + exclude: ['@videojs/react', '@videojs/html'], }, resolve: { dedupe: ['react', 'react-dom'], diff --git a/site/package.json b/site/package.json index 1faa93d75..1516d81da 100644 --- a/site/package.json +++ b/site/package.json @@ -28,9 +28,7 @@ "@sentry/astro": "^10.32.1", "@tailwindcss/vite": "^4.1.17", "@videojs/html": "workspace:*", - "@videojs/html-preview": "workspace:*", "@videojs/react": "workspace:*", - "@videojs/react-preview": "workspace:*", "astro": "^5.14.4", "clsx": "^2.1.1", "es-toolkit": "^1.32.0", diff --git a/site/src/components/HomePageDemo/Base.tsx b/site/src/components/HomePageDemo/Base.tsx index 3b90b86b9..381daff66 100644 --- a/site/src/components/HomePageDemo/Base.tsx +++ b/site/src/components/HomePageDemo/Base.tsx @@ -5,37 +5,42 @@ import { framework, skin } from '@/stores/homePageDemos'; import ClientCode from '../Code/ClientCode'; function generateHTMLCode(skin: Skin): string { - const skinTag = `${skin}-skin`; + const skinTag = skin === 'frosted' ? 'video-skin' : 'minimal-video-skin'; + const skinFile = skin === 'frosted' ? 'skin' : 'minimal-skin'; - return ` + return ` + + + <${skinTag}> - + -`; +`; } function generateReactCode(skin: Skin): string { - const skinComponent = skin === 'frosted' ? 'FrostedSkin' : 'MinimalSkin'; - const skinImport = skin === 'frosted' ? 'frosted' : 'minimal'; + const skinComponent = skin === 'frosted' ? 'VideoSkin' : 'MinimalVideoSkin'; + const skinCss = skin === 'frosted' ? 'skin' : 'minimal-skin'; + + return `import { createPlayer, features, Poster } from '@videojs/react'; +import { ${skinComponent}, Video } from '@videojs/react/video'; +import '@videojs/react/video/${skinCss}.css'; - return `// npm install @videojs/react-preview -import { VideoProvider, ${skinComponent}, Video } from '@videojs/react-preview'; -import '@videojs/react-preview/skins/${skinImport}.css'; +const Player = createPlayer({ features: [...features.video] }); -export const VideoPlayer = () => { +export function VideoPlayer() { return ( - + <${skinComponent}> - + ); -};`; -} - -function generateJS(skin: Skin): string { - return `// npm install @videojs/html-preview -import '@videojs/html-preview/skins/${skin}';`; +}`; } export default function BaseDemo({ className }: { className?: string }) { @@ -49,14 +54,10 @@ export default function BaseDemo({ className }: { className?: string }) { HTML - JavaScript - - - ); } diff --git a/site/src/components/HomePageDemo/Eject.tsx b/site/src/components/HomePageDemo/Eject.tsx index fe768b6bd..19c652781 100644 --- a/site/src/components/HomePageDemo/Eject.tsx +++ b/site/src/components/HomePageDemo/Eject.tsx @@ -2,33 +2,26 @@ import { useStore } from '@nanostores/react'; import { Tab, TabsList, TabsPanel, TabsRoot } from '@/components/Tabs'; import type { Skin } from '@/stores/homePageDemos'; import { framework, skin } from '@/stores/homePageDemos'; -import { - generateHTMLCSS, - generateHTMLJS, - generateHTMLMarkup, - generateReactComponent, - generateReactCSS as genReactCSS, -} from '@/utils/ejectCodeGenerator'; import ClientCode from '../Code/ClientCode'; function generateReactCode(skin: Skin): string { - return generateReactComponent(skin); + return `react`; } function generateReactCSS(skin: Skin): string { - return genReactCSS(skin); + return `css`; } function generateHTMLCode(skin: Skin): string { - return generateHTMLMarkup(skin); + return `html`; } function generateCSS(skin: Skin): string { - return generateHTMLCSS(skin); + return `css`; } function generateJS(skin: Skin): string { - return generateHTMLJS(skin); + return `js`; } export default function EjectDemo({ className }: { className?: string }) { diff --git a/site/src/components/home/HeroVideo.tsx b/site/src/components/home/HeroVideo.tsx index d74fcc335..f02cf2756 100644 --- a/site/src/components/home/HeroVideo.tsx +++ b/site/src/components/home/HeroVideo.tsx @@ -1,24 +1,23 @@ import { useStore } from '@nanostores/react'; -import { FrostedSkin, HlsVideo, MinimalSkin, VideoProvider } from '@videojs/react-preview'; +import { createPlayer, features, Poster } from '@videojs/react'; +import { MinimalVideoSkin, Video, VideoSkin } from '@videojs/react/video'; import { VJS8_DEMO_VIDEO } from '@/consts'; import { skin } from '@/stores/homePageDemos'; -import '@videojs/react-preview/skins/frosted.css'; -import '@videojs/react-preview/skins/minimal.css'; +import '@videojs/react/video/skin.css'; +import '@videojs/react/video/minimal-skin.css'; + +const Player = createPlayer({ features: [...features.video] }); export default function HeroVideo({ className, poster }: { className?: string; poster: string }) { const $skin = useStore(skin); - const SkinComponent = $skin === 'frosted' ? FrostedSkin : MinimalSkin; + const SkinComponent = $skin === 'frosted' ? VideoSkin : MinimalVideoSkin; return ( - + - + - + ); } diff --git a/site/src/content/docs/how-to/customize-skins.mdx b/site/src/content/docs/how-to/customize-skins.mdx index 9605884b0..22188921c 100644 --- a/site/src/content/docs/how-to/customize-skins.mdx +++ b/site/src/content/docs/how-to/customize-skins.mdx @@ -1,100 +1,8 @@ --- title: Customize skins -description: Learn how to customize Video.js v10 skins by copying and modifying them +description: Learn how to customize Video.js skins --- -import FrameworkCase from '@/components/docs/FrameworkCase.astro'; -import ServerCode from '@/components/Code/ServerCode.astro'; -import { TabsRoot, TabsList, TabsPanel, Tab } from '@/components/Tabs.tsx'; -import { - generateHTMLCSS, - generateHTMLJS, - generateHTMLMarkup, - generateReactComponent, - generateReactCSS, -} from '@/utils/ejectCodeGenerator'; - - Video.js v10 comes with pre-built skins like Frosted and Minimal, but you can fully customize them by "ejecting" the code and making it your own. -While eventually we'll have a CLI that'll eject skins in your preferred framework and style, for now we invite you to try it out with these copy-paste-ready implementations. - -## Frosted skin - - - - - - FrostedSkin.tsx - frosted.css - - - - - - - - - - - - - - - - HTML - JS - CSS - - - - - - - - - - - - - - -## Minimal skin - - - - - - MinimalSkin.tsx - minimal.css - - - - - - - - - - - - - - - - HTML - JS - CSS - - - - - - - - - - - - - \ No newline at end of file +🚧 Under construction 🚧 diff --git a/site/src/utils/ejectCodeGenerator.ts b/site/src/utils/ejectCodeGenerator.ts deleted file mode 100644 index 4eabf020c..000000000 --- a/site/src/utils/ejectCodeGenerator.ts +++ /dev/null @@ -1,1916 +0,0 @@ -/** - * Shared code generation module for ejected skin examples. - * This is the single source of truth for code displayed on the home page demo and in docs. - */ - -type Skin = 'frosted' | 'minimal'; - -/** - * Generate React component code for the specified skin. - * Always uses .m3u8 video source. - */ -export function generateReactComponent(skin: Skin): string { - if (skin === 'frosted') { - return `// npm install @videojs/react-preview -import type { PropsWithChildren } from 'react'; - -import { CurrentTimeDisplay, DurationDisplay, FullscreenButton, MediaContainer, MuteButton, PlayButton, Popover, PreviewTimeDisplay, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react-preview'; -import { - FullscreenEnterIcon, - FullscreenExitIcon, - PauseIcon, - PlayIcon, - VolumeHighIcon, - VolumeLowIcon, - VolumeOffIcon, -} from '@videojs/react-preview/icons'; - -import './frosted.css'; - -type SkinProps = PropsWithChildren<{ - className?: string; -}>; - -export default function FrostedSkin({ children, className = '' }: SkinProps): JSX.Element { - return ( - - {children} - -
- -
- - - - - - - - - - Play - Pause - - - - -
- - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Enter Fullscreen - Exit Fullscreen - - - -
- - ); -}`; - } else { - return `// npm install @videojs/react-preview -import type { PropsWithChildren } from 'react'; - -import { CurrentTimeDisplay, DurationDisplay, FullscreenButton, MediaContainer, MuteButton, PlayButton, Popover, PreviewTimeDisplay, TimeSlider, Tooltip, VolumeSlider } from '@videojs/react-preview'; -import { - FullscreenEnterAltIcon, - FullscreenExitAltIcon, - PauseIcon, - PlayIcon, - VolumeHighIcon, - VolumeLowIcon, - VolumeOffIcon, -} from '@videojs/react-preview/icons'; - -import './minimal.css'; - -type SkinProps = PropsWithChildren<{ - className?: string; -}>; - -export default function MinimalSkin({ children, className = '' }: SkinProps): JSX.Element { - return ( - - {children} - -
- -
- - - - - - - - - - Play - Pause - - - - -
- - - - / - - -
- - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Enter Fullscreen - Exit Fullscreen - - - -
-
- - ); -}`; - } -} - -/** - * Generate React CSS code for the specified skin. - */ -export function generateReactCSS(skin: Skin): string { - if (skin === 'frosted') { - return `.vjs-frosted-skin * { - box-sizing: border-box; -} - -.vjs-frosted-skin { - position: relative; - isolation: isolate; - container: root / inline-size; - overflow: clip; - font-size: 0.8125rem; - line-height: 1.5; - border-radius: inherit; - background: oklab(0 0 0); -} -.vjs-frosted-skin::before, -.vjs-frosted-skin::after { - content: ''; - position: absolute; - pointer-events: none; - border-radius: inherit; - z-index: 10; -} -.vjs-frosted-skin::before { - inset: 1px; - box-shadow: inset 0 0 0 1px oklab(1 0 0 / 0.15); -} -.vjs-frosted-skin::after { - inset: 0; - box-shadow: inset 0 0 0 1px oklab(0 0 0 / 0.1); -} - -/* Fullscreen */ -.vjs-frosted-skin:fullscreen { - border-radius: 0; -} - -.vjs-frosted-skin > ::slotted([slot='media']) { - display: block; - width: 100%; - height: 100%; -} - -/* Media Container UI Overlay Styling */ -.vjs-frosted-skin > .overlay { - position: absolute; - inset: 0; - display: flex; - flex-flow: column nowrap; - align-items: start; - pointer-events: none; - border-radius: inherit; - background-image: linear-gradient(to top, oklab(0 0 0 / 0.5), oklab(0 0 0 / 0.3), rgba(0, 0, 0, 0)); - backdrop-filter: saturate(1.5) brightness(0.9); - transition: opacity 0.15s ease-out; - transition-delay: 500ms; - opacity: 0; -} -.vjs-frosted-skin:hover > .overlay, -.vjs-frosted-skin:has([data-paused]) > .overlay, -.vjs-frosted-skin:has([aria-expanded='true']) > .overlay { - opacity: 1; - transition-duration: 100ms; - transition-delay: 0ms; -} - -/* Common Surface Styles - e.g. tooltips, popovers, controls */ -.vjs-frosted-skin .surface { - background-color: oklab(1 0 0 / 0.1); - backdrop-filter: blur(64px) brightness(0.9) saturate(1.5); - box-shadow: - inset 0 0 0 1px oklab(1 0 0 / 0.15), - 0 0 0 1px oklab(0 0 0 / 0.15), - oklab(0 0 0 / 0.15) 0px 1px 3px 0px, - oklab(0 0 0 / 0.15) 0px 1px 2px -1px; -} -@media (prefers-reduced-transparency: reduce) { - .vjs-frosted-skin .surface { - background-color: oklab(0 0 0 / 0.7); - box-shadow: - inset 0 0 0 1px oklab(0 0 0), - 0 0 0 1px oklab(1 0 0 / 0.2); - } -} -@media (prefers-contrast: more) { - .vjs-frosted-skin .surface { - background-color: oklab(0 0 0 / 0.9); - box-shadow: - inset 0 0 0 1px oklab(0 0 0), - 0 0 0 1px oklab(1 0 0 / 0.2); - } -} - -/* Media Control Bar UI/Styles */ -.vjs-frosted-skin .control-bar { - position: absolute; - bottom: 0.75rem; - inset-inline: 0.75rem; - padding: 0.25rem; - display: flex; - align-items: center; - gap: 0.125rem; - border-radius: calc(infinity * 1px); - will-change: scale, transform, filter, opacity; - scale: 0.9; - opacity: 0; - filter: blur(8px); - transition-property: scale, transform, filter, opacity; - transition-delay: 500ms; - transition-duration: 300ms; - transition-timing-function: ease-out; - transform-origin: bottom; - color: oklab(1 0 0); -} -.vjs-frosted-skin:hover > .control-bar, -.vjs-frosted-skin:has([data-paused]) > .control-bar, -.vjs-frosted-skin:has([aria-expanded='true']) > .control-bar { - opacity: 1; - scale: 1; - filter: blur(0px); - transition-delay: 0ms; - transition-duration: 100ms; -} - -/* Time Display Styling */ -.vjs-frosted-skin .time-controls { - display: flex; - align-items: center; - flex: 1; - gap: 0.75rem; - padding-inline: 0.375rem; -} -.vjs-frosted-skin .time-display { - text-shadow: 0 1px 0 oklab(0 0 0 / 0.25); - font-variant-numeric: tabular-nums; -} - -/* Generic Media Button Styling */ -.vjs-frosted-skin .button { - display: grid; - flex-shrink: 0; - padding: 0.5rem; - cursor: pointer; - background: transparent; - border: none; - border-radius: calc(infinity * 1px); - color: oklab(1 0 0 / 0.9); - user-select: none; - outline: 2px solid transparent; - outline-offset: -2px; - transition-property: background-color, color, outline-offset; - transition-duration: 150ms; - transition-timing-function: ease-out; -} -.vjs-frosted-skin .button:hover, -.vjs-frosted-skin .button:focus-visible, -.vjs-frosted-skin .button[aria-expanded='true'] { - background-color: oklab(1 0 0 / 0.1); - color: oklab(1 0 0); - text-decoration: none; -} -.vjs-frosted-skin .button:focus-visible { - outline-color: oklch(62.3% 0.214 259.815); - outline-offset: 2px; -} -.vjs-frosted-skin .button[disabled] { - cursor: not-allowed; - opacity: 0.5; - filter: grayscale(1); -} - -.vjs-frosted-skin .button .icon { - grid-area: 1 / 1; - width: 18px; - height: 18px; - filter: drop-shadow(0 1px 0 oklab(0 0 0 / 0.25)); -} - -/* Media Play Button UI/Styles */ -.vjs-frosted-skin .play-button .icon { - opacity: 0; - transition: opacity 150ms linear; -} - -.vjs-frosted-skin .play-button:not([data-paused]) .pause-icon, -.vjs-frosted-skin .play-button[data-paused] .play-icon { - opacity: 1; -} - -/* Media Fullscreen Button UI/Styles */ -.vjs-frosted-skin .fullscreen-button .icon { - display: none; -} -.vjs-frosted-skin .fullscreen-button:not([data-fullscreen]) .fullscreen-enter-icon, -.vjs-frosted-skin .fullscreen-button[data-fullscreen] .fullscreen-exit-icon { - display: inline; -} - -/* One way to define the "default visible" icon (CJP) */ -.vjs-frosted-skin .mute-button .icon { - display: none; -} -.vjs-frosted-skin .mute-button:not([data-volume-level]) .volume-low-icon, -.vjs-frosted-skin .mute-button[data-volume-level='high'] .volume-high-icon, -.vjs-frosted-skin .mute-button[data-volume-level='low'] .volume-low-icon, -.vjs-frosted-skin .mute-button[data-volume-level='medium'] .volume-low-icon, -.vjs-frosted-skin .mute-button[data-volume-level='off'] .volume-off-icon { - display: inline; -} - -/* TimeSlider Component Styles */ -.vjs-frosted-skin .slider { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - position: relative; - border-radius: calc(infinity * 1px); - outline: none; -} - -/* Horizontal orientation styles */ -.vjs-frosted-skin .slider[data-orientation='horizontal'] { - min-width: 5rem; - width: 100%; - height: 1.25rem; -} - -/* Vertical orientation styles */ -.vjs-frosted-skin .slider[data-orientation='vertical'] { - height: 5rem; - width: 1.25rem; -} - -.vjs-frosted-skin .slider-track { - position: relative; - isolation: isolate; - background-color: oklab(1 0 0 / 0.2); - border-radius: inherit; - overflow: hidden; - user-select: none; - outline: 2px solid transparent; - outline-offset: -2px; - transition: outline-offset 150ms ease-out; - box-shadow: 0 0 0 1px oklab(0 0 0 / 0.05); -} - -/* Horizontal track styles */ -.vjs-frosted-skin .slider-track[data-orientation='horizontal'] { - width: 100%; - height: 0.25rem; -} - -/* Vertical track styles */ -.vjs-frosted-skin .slider-track[data-orientation='vertical'] { - width: 0.25rem; - height: 100%; -} - -.vjs-frosted-skin .slider:focus-visible .slider-track { - outline-color: oklch(62.3% 0.214 259.815); - outline-offset: 6px; -} - -.vjs-frosted-skin .slider-thumb { - width: 0.625rem; - height: 0.625rem; - background-color: oklab(1 0 0); - border-radius: calc(infinity * 1px); - user-select: none; - z-index: 10; - box-shadow: - 0 0 0 1px oklab(0 0 0 / 0.1), - 0 1px 3px 0 oklab(0 0 0 / 0.15), - 0 1px 2px -1px oklab(0 0 0 / 0.15); - opacity: 0; - transition-property: opacity, height, width; - transition-duration: 150ms; - transition-timing-function: ease-out; -} -.vjs-frosted-skin .slider-thumb:active { - width: 0.75rem; - height: 0.75rem; -} -.vjs-frosted-skin .slider:hover .slider-thumb, -.vjs-frosted-skin .slider:focus-within .slider-thumb { - opacity: 1; -} -.vjs-frosted-skin .slider-track[data-orientation='horizontal'] .slider-thumb { - cursor: ew-resize; -} -.vjs-frosted-skin .slider-track[data-orientation='vertical'] .slider-thumb { - cursor: ns-resize; -} - -.vjs-frosted-skin .slider-pointer { - background-color: oklab(1 0 0 / 0.2); - border-radius: inherit; -} - -.vjs-frosted-skin .slider-progress { - background-color: oklab(1 0 0); - border-radius: inherit; -} - -.vjs-frosted-skin .media-preview-time-display { - font-variant-numeric: tabular-nums; -} - -.vjs-frosted-skin .popup-animation { - transition-property: transform, scale, opacity, filter; - transition-duration: 200ms; - transform: scale(1); - transform-origin: bottom; - opacity: 1; - filter: blur(0px); -} -.vjs-frosted-skin .popup-animation[data-starting-style], -.vjs-frosted-skin .popup-animation[data-ending-style] { - transform: scale(0); - opacity: 0; - filter: blur(8px); -} -.vjs-frosted-skin .popup-animation[data-instant] { - transition-duration: 0ms; -} - -.vjs-frosted-skin .popover-popup { - margin: 0; - border: none; - box-shadow: none; - background: transparent; - padding: 0.75rem 0.25rem; - border-radius: calc(infinity * 1px); -} - -/* Tooltip Component Styles */ -.vjs-frosted-skin .tooltip-popup { - color: oklab(1 0 0); - padding: 0.25rem 0.625rem; - border-radius: calc(infinity * 1px); - font-size: 0.75rem; -} - -.vjs-frosted-skin .tooltip { - display: none; - white-space: nowrap; -} - -.vjs-frosted-skin .tooltip-popup[data-paused] .play-tooltip, -.vjs-frosted-skin .tooltip-popup:not([data-paused]) .pause-tooltip { - display: block; -} - -.vjs-frosted-skin .tooltip-popup[data-fullscreen] .fullscreen-exit-tooltip, -.vjs-frosted-skin .tooltip-popup:not([data-fullscreen]) .fullscreen-enter-tooltip { - display: block; -}`; - } else { - return `.vjs-minimal-skin * { - box-sizing: border-box; -} - -.vjs-minimal-skin { - position: relative; - isolation: isolate; - container: root / inline-size; - overflow: clip; - font-size: 0.8125rem; - line-height: 1.5; - border-radius: inherit; - background: oklab(0 0 0); -} -.vjs-minimal-skin::after { - content: ''; - position: absolute; - pointer-events: none; - border-radius: inherit; - z-index: 10; - inset: 0; - box-shadow: inset 0 0 0 1px oklab(0 0 0 / 0.2); -} -@media (prefers-color-scheme: dark) { - .vjs-minimal-skin::after { - box-shadow: inset 0 0 0 1px oklab(1 0 0 / 0.2); - } -} - -/* Fullscreen */ -.vjs-minimal-skin:fullscreen { - border-radius: 0; -} - -.vjs-minimal-skin > ::slotted([slot='media']) { - display: block; - width: 100%; - height: 100%; -} - -/* Media Container UI Overlay Styling */ -.vjs-minimal-skin > .overlay { - position: absolute; - inset: 0; - display: flex; - flex-flow: column nowrap; - align-items: start; - pointer-events: none; - border-radius: inherit; - background-image: linear-gradient(to top, oklab(0 0 0 / 0.7), oklab(0 0 0 / 0.5) 7.5rem, rgba(0, 0, 0, 0)); - transform: translateY(100%); - transition-property: transform, opacity; - transition-duration: 150ms; - transition-timing-function: ease-out; - transition-delay: 500ms; - opacity: 0; -} -.vjs-minimal-skin:hover > .overlay, -.vjs-minimal-skin:has([data-paused]) > .overlay, -.vjs-minimal-skin:has([aria-expanded='true']) > .overlay { - opacity: 1; - transform: translateY(0); - transition-duration: 100ms; - transition-delay: 0ms; -} - -/* Media Control Bar UI/Styles */ -.vjs-minimal-skin .control-bar { - position: absolute; - bottom: 0; - inset-inline: 0; - padding: 2.5rem 0.75rem 0.75rem 0.75rem; - display: flex; - align-items: center; - gap: 0.875rem; - will-change: transform, filter, opacity; - transform: translateY(100%); - opacity: 0; - filter: blur(8px); - transition-property: transform, filter, opacity; - transition-delay: 500ms; - transition-duration: 300ms; - transition-timing-function: ease-out; - color: oklab(1 0 0); -} -.vjs-minimal-skin:hover > .control-bar, -.vjs-minimal-skin:has([data-paused]) > .control-bar, -.vjs-minimal-skin:has([aria-expanded='true']) > .control-bar { - opacity: 1; - transform: translateY(0); - filter: blur(0px); - transition-delay: 0ms; - transition-duration: 100ms; -} - -/* Time Display Styling */ -.vjs-minimal-skin .time-display-group { - display: flex; - align-items: center; - gap: 0.25rem; -} -.vjs-minimal-skin .duration-display { - display: contents; - color: oklab(1 0 0 / 0.5); -} -.vjs-minimal-skin .time-display { - text-shadow: 0 1px 0 oklab(0 0 0 / 0.2); - font-variant-numeric: tabular-nums; -} - -.vjs-minimal-skin .button-group { - display: flex; - align-items: center; - gap: 0.375rem; -} - -/* Generic Media Button Styling */ -.vjs-minimal-skin .button { - display: grid; - flex-shrink: 0; - padding: 0.625rem; - cursor: pointer; - background: transparent; - border: none; - border-radius: 0.375rem; - color: oklab(1 0 0); - user-select: none; - outline: 2px solid transparent; - outline-offset: -2px; - transition-property: background-color, color, outline-offset; - transition-duration: 150ms; - transition-timing-function: ease-out; -} -.vjs-minimal-skin .button:hover, -.vjs-minimal-skin .button:focus-visible, -.vjs-minimal-skin .button[aria-expanded='true'] { - color: oklab(1 0 0 / 0.8); - text-decoration: none; -} -.vjs-minimal-skin .button:focus-visible { - outline-color: oklab(1 0 0); - outline-offset: 2px; -} -.vjs-minimal-skin .button[disabled] { - cursor: not-allowed; - opacity: 0.5; - filter: grayscale(1); -} - -.vjs-minimal-skin .button .icon { - grid-area: 1 / 1; - width: 18px; - height: 18px; - filter: drop-shadow(0 1px 0 oklab(0 0 0 / 0.4)); -} - -/* Media Play Button UI/Styles */ -.vjs-minimal-skin .play-button .icon { - opacity: 0; - transition: opacity 150ms linear; -} - -.vjs-minimal-skin .play-button:not([data-paused]) .pause-icon, -.vjs-minimal-skin .play-button[data-paused] .play-icon { - opacity: 1; -} - -/* Media Fullscreen Button UI/Styles */ -.vjs-minimal-skin .fullscreen-button .icon { - display: none; -} -.vjs-minimal-skin .fullscreen-button:not([data-fullscreen]) .fullscreen-enter-icon, -.vjs-minimal-skin .fullscreen-button[data-fullscreen] .fullscreen-exit-icon { - display: inline; -} - -/* One way to define the "default visible" icon (CJP) */ -.vjs-minimal-skin .mute-button .icon { - display: none; -} -.vjs-minimal-skin .mute-button:not([data-volume-level]) .volume-low-icon, -.vjs-minimal-skin .mute-button[data-volume-level='high'] .volume-high-icon, -.vjs-minimal-skin .mute-button[data-volume-level='low'] .volume-low-icon, -.vjs-minimal-skin .mute-button[data-volume-level='medium'] .volume-low-icon, -.vjs-minimal-skin .mute-button[data-volume-level='off'] .volume-off-icon { - display: inline; -} - -/* TimeSlider Component Styles */ -.vjs-minimal-skin .slider { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - position: relative; - border-radius: calc(infinity * 1px); - outline: none; -} - -/* Horizontal orientation styles */ -.vjs-minimal-skin .slider[data-orientation='horizontal'] { - min-width: 5rem; - width: 100%; - height: 1.25rem; -} - -/* Vertical orientation styles */ -.vjs-minimal-skin .slider[data-orientation='vertical'] { - height: 4.5rem; - width: 1.25rem; -} - -.vjs-minimal-skin .slider-track { - position: relative; - isolation: isolate; - background-color: oklab(1 0 0 / 0.2); - border-radius: inherit; - overflow: hidden; - user-select: none; - outline: 2px solid transparent; - outline-offset: -2px; - transition: outline-offset 150ms ease-out; - box-shadow: 0 0 0 1px oklab(0 0 0 / 0.05); -} - -/* Horizontal track styles */ -.vjs-minimal-skin .slider-track[data-orientation='horizontal'] { - width: 100%; - height: 0.1875rem; -} - -/* Vertical track styles */ -.vjs-minimal-skin .slider-track[data-orientation='vertical'] { - width: 0.1875rem; - height: 100%; -} - -.vjs-minimal-skin .slider:focus-visible .slider-track { - outline-color: oklab(1 0 0); - outline-offset: 6px; -} - -.vjs-minimal-skin .slider-thumb { - width: 0.75rem; - height: 0.75rem; - background-color: oklab(1 0 0); - border-radius: calc(infinity * 1px); - user-select: none; - z-index: 10; - box-shadow: - 0 0 0 1px oklab(0 0 0 / 0.1), - 0 1px 3px 0 oklab(0 0 0 / 0.15), - 0 1px 2px -1px oklab(0 0 0 / 0.15); - opacity: 0; - scale: 0.7; - transform-origin: center; - transition-property: opacity, scale; - transition-duration: 150ms; - transition-timing-function: ease-out; -} -.vjs-minimal-skin .slider:hover .slider-thumb, -.vjs-minimal-skin .slider:focus-within .slider-thumb { - opacity: 1; - scale: 1; -} -.vjs-minimal-skin .slider-track[data-orientation='horizontal'] .slider-thumb { - cursor: ew-resize; -} -.vjs-minimal-skin .slider-track[data-orientation='vertical'] .slider-thumb { - cursor: ns-resize; -} - -.vjs-minimal-skin .slider-pointer { - display: none; -} - -.vjs-minimal-skin .slider-progress { - background-color: oklab(1 0 0); - border-radius: inherit; -} - -.vjs-minimal-skin .media-preview-time-display { - font-variant-numeric: tabular-nums; -} - -.vjs-minimal-skin .popup-animation { - transition-property: transform, scale, opacity, filter; - transition-duration: 200ms; - transform: scale(1); - transform-origin: bottom; - opacity: 1; - filter: blur(0px); -} -.vjs-minimal-skin .popup-animation[data-starting-style], -.vjs-minimal-skin .popup-animation[data-ending-style] { - transform: scale(0); - opacity: 0; - filter: blur(8px); -} -.vjs-minimal-skin .popup-animation[data-instant] { - transition-duration: 0ms; -} - -.vjs-minimal-skin .popover-popup { - margin: 0; - border: none; - box-shadow: none; - background: transparent; - padding: 0.75rem 0.25rem; - border-radius: calc(infinity * 1px); -} - -/* Tooltip Component Styles */ -.vjs-minimal-skin .tooltip-popup { - color: oklab(1 0 0); - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; - font-size: 0.75rem; - background-color: oklab(1 0 0 / 0.1); - backdrop-filter: blur(64px) brightness(0.9) saturate(1.5); - box-shadow: - 0 4px 6px -1px oklab(0 0 0 / 0.1), - 0 2px 4px -2px oklab(0 0 0 / 0.1); -} -@media (prefers-reduced-transparency: reduce) { - .vjs-minimal-skin .tooltip-popup { - background-color: oklab(0 0 0 / 0.7); - } -} -@media (prefers-contrast: more) { - .vjs-minimal-skin .tooltip-popup { - background-color: oklab(0 0 0 / 0.9); - } -} - -.vjs-minimal-skin .tooltip { - display: none; - white-space: nowrap; -} - -.vjs-minimal-skin .tooltip-popup[data-paused] .play-tooltip, -.vjs-minimal-skin .tooltip-popup:not([data-paused]) .pause-tooltip { - display: block; -} - -.vjs-minimal-skin .tooltip-popup[data-fullscreen] .fullscreen-exit-tooltip, -.vjs-minimal-skin .tooltip-popup:not([data-fullscreen]) .fullscreen-enter-tooltip { - display: block; -}`; - } -} - -/** - * Generate HTML markup for the specified skin. - * Always uses .mp4 video source. - */ -export function generateHTMLMarkup(skin: Skin): string { - if (skin === 'frosted') { - return ` - - - -
- -
- - - - - - Play - Pause - - -
- - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - Enter Fullscreen - Exit Fullscreen - -
-
-
`; - } else { - return ` - - - -
- -
- - - - - - Play - Pause - - -
- - - - - / - - -
- - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - Enter Fullscreen - Exit Fullscreen - -
-
-
-
`; - } -} - -/** - * Generate HTML CSS code for the specified skin. - */ -export function generateHTMLCSS(skin: Skin): string { - // HTML CSS is nearly identical to React CSS, with just element selectors instead of class selectors - if (skin === 'frosted') { - return `media-container * { - box-sizing: border-box; -} - -media-container { - position: relative; - isolation: isolate; - container: root / inline-size; - overflow: clip; - font-size: 0.8125rem; - line-height: 1.5; - border-radius: inherit; - background: oklab(0 0 0); -} -media-container::before, -media-container::after { - content: ''; - position: absolute; - pointer-events: none; - border-radius: inherit; - z-index: 10; -} -media-container::before { - inset: 1px; - box-shadow: inset 0 0 0 1px oklab(1 0 0 / 0.15); -} -media-container::after { - inset: 0; - box-shadow: inset 0 0 0 1px oklab(0 0 0 / 0.1); -} - -/* Fullscreen */ -media-container:fullscreen { - border-radius: 0; -} - -media-container > ::slotted([slot='media']), -media-container > video, -media-container > audio { - display: block; - width: 100%; - height: 100%; -} - -/* Media Container UI Overlay Styling */ -media-container > .overlay { - position: absolute; - inset: 0; - display: flex; - flex-flow: column nowrap; - align-items: start; - pointer-events: none; - border-radius: inherit; - background-image: linear-gradient(to top, oklab(0 0 0 / 0.5), oklab(0 0 0 / 0.3), rgba(0, 0, 0, 0)); - backdrop-filter: saturate(1.5) brightness(0.9); - transition: opacity 0.15s ease-out; - transition-delay: 500ms; - opacity: 0; -} -media-container:hover > .overlay, -media-container:has([data-paused]) > .overlay, -media-container:has([aria-expanded='true']) > .overlay { - opacity: 1; - transition-duration: 100ms; - transition-delay: 0ms; -} - -/* Common Surface Styles - e.g. tooltips, popovers, controls */ -.surface { - background-color: oklab(1 0 0 / 0.1); - backdrop-filter: blur(64px) brightness(0.9) saturate(1.5); - box-shadow: - inset 0 0 0 1px oklab(1 0 0 / 0.15), - 0 0 0 1px oklab(0 0 0 / 0.15), - oklab(0 0 0 / 0.15) 0px 1px 3px 0px, - oklab(0 0 0 / 0.15) 0px 1px 2px -1px; -} -@media (prefers-reduced-transparency: reduce) { - .surface { - background-color: oklab(0 0 0 / 0.7); - box-shadow: - inset 0 0 0 1px oklab(0 0 0), - 0 0 0 1px oklab(1 0 0 / 0.2); - } -} -@media (prefers-contrast: more) { - .surface { - background-color: oklab(0 0 0 / 0.9); - box-shadow: - inset 0 0 0 1px oklab(0 0 0), - 0 0 0 1px oklab(1 0 0 / 0.2); - } -} - -/* Media Control Bar UI/Styles */ -.control-bar { - position: absolute; - bottom: 0.75rem; - inset-inline: 0.75rem; - padding: 0.25rem; - display: flex; - align-items: center; - gap: 0.125rem; - border-radius: calc(infinity * 1px); - will-change: scale, transform, filter, opacity; - scale: 0.9; - opacity: 0; - filter: blur(8px); - transition-property: scale, transform, filter, opacity; - transition-delay: 500ms; - transition-duration: 300ms; - transition-timing-function: ease-out; - transform-origin: bottom; - color: oklab(1 0 0); -} -media-container:hover > .control-bar, -media-container:has([data-paused]) > .control-bar, -media-container:has([aria-expanded='true']) > .control-bar { - opacity: 1; - scale: 1; - filter: blur(0px); - transition-delay: 0ms; - transition-duration: 100ms; -} - -/* Time Display Styling */ -.time-controls { - display: flex; - align-items: center; - flex: 1; - gap: 0.75rem; - padding-inline: 0.375rem; -} -media-current-time-display, -media-duration-display { - text-shadow: 0 1px 0 oklab(0 0 0 / 0.25); - font-variant-numeric: tabular-nums; -} - -/* Generic Media Button Styling */ -.button { - display: grid; - flex-shrink: 0; - padding: 0.5rem; - cursor: pointer; - background: transparent; - border: none; - border-radius: calc(infinity * 1px); - color: oklab(1 0 0 / 0.9); - user-select: none; - outline: 2px solid transparent; - outline-offset: -2px; - transition-property: background-color, color, outline-offset; - transition-duration: 150ms; - transition-timing-function: ease-out; -} -.button:hover, -.button:focus-visible, -.button[aria-expanded='true'] { - background-color: oklab(1 0 0 / 0.1); - color: oklab(1 0 0); - text-decoration: none; -} -.button:focus-visible { - outline-color: oklch(62.3% 0.214 259.815); - outline-offset: 2px; -} -.button[disabled] { - cursor: not-allowed; - opacity: 0.5; - filter: grayscale(1); -} - -.button .icon { - grid-area: 1 / 1; - width: 18px; - height: 18px; - filter: drop-shadow(0 1px 0 oklab(0 0 0 / 0.25)); -} - -/* Media Play Button UI/Styles */ -media-play-button .icon { - opacity: 0; - transition: opacity 150ms linear; -} - -media-play-button:not([data-paused]) .pause-icon, -media-play-button[data-paused] .play-icon { - opacity: 1; -} - -/* Media Fullscreen Button UI/Styles */ -media-fullscreen-button .icon { - display: none; -} -media-fullscreen-button:not([data-fullscreen]) .fullscreen-enter-icon, -media-fullscreen-button[data-fullscreen] .fullscreen-exit-icon { - display: inline; -} - -/* One way to define the "default visible" icon (CJP) */ -media-mute-button .icon { - display: none; -} -media-mute-button:not([data-volume-level]) .volume-low-icon, -media-mute-button[data-volume-level='high'] .volume-high-icon, -media-mute-button[data-volume-level='low'] .volume-low-icon, -media-mute-button[data-volume-level='medium'] .volume-low-icon, -media-mute-button[data-volume-level='off'] .volume-off-icon { - display: inline; -} - -/* TimeSlider Component Styles */ -.slider { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - position: relative; - border-radius: calc(infinity * 1px); - outline: none; -} - -/* Horizontal orientation styles */ -.slider[data-orientation='horizontal'] { - min-width: 5rem; - width: 100%; - height: 1.25rem; -} - -/* Vertical orientation styles */ -.slider[data-orientation='vertical'] { - height: 5rem; - width: 1.25rem; -} - -.slider-track { - position: relative; - isolation: isolate; - background-color: oklab(1 0 0 / 0.2); - border-radius: inherit; - overflow: hidden; - user-select: none; - outline: 2px solid transparent; - outline-offset: -2px; - transition: outline-offset 150ms ease-out; - box-shadow: 0 0 0 1px oklab(0 0 0 / 0.05); -} - -/* Horizontal track styles */ -.slider-track[data-orientation='horizontal'] { - width: 100%; - height: 0.25rem; -} - -/* Vertical track styles */ -.slider-track[data-orientation='vertical'] { - width: 0.25rem; - height: 100%; -} - -.slider:focus-visible .slider-track { - outline-color: oklch(62.3% 0.214 259.815); - outline-offset: 6px; -} - -.slider-thumb { - width: 0.625rem; - height: 0.625rem; - background-color: oklab(1 0 0); - border-radius: calc(infinity * 1px); - user-select: none; - z-index: 10; - box-shadow: - 0 0 0 1px oklab(0 0 0 / 0.1), - 0 1px 3px 0 oklab(0 0 0 / 0.15), - 0 1px 2px -1px oklab(0 0 0 / 0.15); - opacity: 0; - transition-property: opacity, height, width; - transition-duration: 150ms; - transition-timing-function: ease-out; -} -.slider-thumb:active { - width: 0.75rem; - height: 0.75rem; -} -.slider:hover .slider-thumb, -.slider:focus-within .slider-thumb { - opacity: 1; -} -.slider-track[data-orientation='horizontal'] .slider-thumb { - cursor: ew-resize; -} -.slider-track[data-orientation='vertical'] .slider-thumb { - cursor: ns-resize; -} - -.slider-pointer { - background-color: oklab(1 0 0 / 0.2); - border-radius: inherit; -} - -.slider-progress { - background-color: oklab(1 0 0); - border-radius: inherit; -} - -media-preview-time-display { - font-variant-numeric: tabular-nums; -} - -.popup-animation { - transition-property: transform, scale, opacity, filter; - transition-duration: 200ms; - transform: scale(1); - transform-origin: bottom; - opacity: 1; - filter: blur(0px); -} -.popup-animation[data-starting-style], -.popup-animation[data-ending-style] { - transform: scale(0); - opacity: 0; - filter: blur(8px); -} -.popup-animation[data-instant] { - transition-duration: 0ms; -} - -media-popover { - margin: 0; - border: none; - box-shadow: none; - background: transparent; - padding: 0.75rem 0.25rem; - border-radius: calc(infinity * 1px); -} - -/* Tooltip Component Styles */ -media-tooltip { - margin: 0; - border: none; - box-shadow: none; - background: transparent; - color: oklab(1 0 0); - padding: 0.25rem 0.625rem; - border-radius: calc(infinity * 1px); - font-size: 0.75rem; -} - -.tooltip { - display: none; - white-space: nowrap; -} - -[data-paused] + media-tooltip .play-tooltip, -:not([data-paused]) + media-tooltip .pause-tooltip { - display: block; -} - -[data-fullscreen] + media-tooltip .fullscreen-exit-tooltip, -:not([data-fullscreen]) + media-tooltip .fullscreen-enter-tooltip { - display: block; -}`; - } else { - return `media-container * { - box-sizing: border-box; -} - -media-container { - position: relative; - isolation: isolate; - container: root / inline-size; - overflow: clip; - font-size: 0.8125rem; - line-height: 1.5; - border-radius: inherit; - background: oklab(0 0 0); -} -media-container::after { - content: ''; - position: absolute; - pointer-events: none; - border-radius: inherit; - z-index: 10; - inset: 0; - box-shadow: inset 0 0 0 1px oklab(0 0 0 / 0.2); -} -@media (prefers-color-scheme: dark) { - media-container::after { - box-shadow: inset 0 0 0 1px oklab(1 0 0 / 0.2); - } -} - -/* Fullscreen */ -media-container:fullscreen { - border-radius: 0; -} - -media-container > ::slotted([slot='media']), -media-container > video, -media-container > audio { - display: block; - width: 100%; - height: 100%; -} - -/* Media Container UI Overlay Styling */ -media-container > .overlay { - position: absolute; - inset: 0; - display: flex; - flex-flow: column nowrap; - align-items: start; - pointer-events: none; - border-radius: inherit; - background-image: linear-gradient(to top, oklab(0 0 0 / 0.7), oklab(0 0 0 / 0.5) 7.5rem, rgba(0, 0, 0, 0)); - transform: translateY(100%); - transition-property: transform, opacity; - transition-duration: 150ms; - transition-timing-function: ease-out; - transition-delay: 500ms; - opacity: 0; -} -media-container:hover > .overlay, -media-container:has([data-paused]) > .overlay, -media-container:has([aria-expanded='true']) > .overlay { - opacity: 1; - transform: translateY(0); - transition-duration: 100ms; - transition-delay: 0ms; -} - -/* Media Control Bar UI/Styles */ -.control-bar { - position: absolute; - bottom: 0; - inset-inline: 0; - padding: 2.5rem 0.75rem 0.75rem 0.75rem; - display: flex; - align-items: center; - gap: 0.875rem; - will-change: transform, filter, opacity; - transform: translateY(100%); - opacity: 0; - filter: blur(8px); - transition-property: transform, filter, opacity; - transition-delay: 500ms; - transition-duration: 300ms; - transition-timing-function: ease-out; - color: oklab(1 0 0); -} -media-container:hover > .control-bar, -media-container:has([data-paused]) > .control-bar, -media-container:has([aria-expanded='true']) > .control-bar { - opacity: 1; - transform: translateY(0); - filter: blur(0px); - transition-delay: 0ms; - transition-duration: 100ms; -} - -/* Time Display Styling */ -.time-display-group { - display: flex; - align-items: center; - gap: 0.25rem; -} -.duration-display { - display: contents; - color: oklab(1 0 0 / 0.5); -} -media-current-time-display, -media-duration-display { - text-shadow: 0 1px 0 oklab(0 0 0 / 0.2); - font-variant-numeric: tabular-nums; -} - -.button-group { - display: flex; - align-items: center; - gap: 0.375rem; -} - -/* Generic Media Button Styling */ -.button { - display: grid; - flex-shrink: 0; - padding: 0.625rem; - cursor: pointer; - background: transparent; - border: none; - border-radius: 0.375rem; - color: oklab(1 0 0); - user-select: none; - outline: 2px solid transparent; - outline-offset: -2px; - transition-property: background-color, color, outline-offset; - transition-duration: 150ms; - transition-timing-function: ease-out; -} -.button:hover, -.button:focus-visible, -.button[aria-expanded='true'] { - color: oklab(1 0 0 / 0.8); - text-decoration: none; -} -.button:focus-visible { - outline-color: oklab(1 0 0); - outline-offset: 2px; -} -.button[disabled] { - cursor: not-allowed; - opacity: 0.5; - filter: grayscale(1); -} - -.button .icon { - grid-area: 1 / 1; - width: 18px; - height: 18px; - filter: drop-shadow(0 1px 0 oklab(0 0 0 / 0.4)); -} - -/* Media Play Button UI/Styles */ -media-play-button .icon { - opacity: 0; - transition: opacity 150ms linear; -} - -media-play-button:not([data-paused]) .pause-icon, -media-play-button[data-paused] .play-icon { - opacity: 1; -} - -/* Media Fullscreen Button UI/Styles */ -media-fullscreen-button .icon { - display: none; -} -media-fullscreen-button:not([data-fullscreen]) .fullscreen-enter-icon, -media-fullscreen-button[data-fullscreen] .fullscreen-exit-icon { - display: inline; -} - -/* One way to define the "default visible" icon (CJP) */ -media-mute-button .icon { - display: none; -} -media-mute-button:not([data-volume-level]) .volume-low-icon, -media-mute-button[data-volume-level='high'] .volume-high-icon, -media-mute-button[data-volume-level='low'] .volume-low-icon, -media-mute-button[data-volume-level='medium'] .volume-low-icon, -media-mute-button[data-volume-level='off'] .volume-off-icon { - display: inline; -} - -/* TimeSlider Component Styles */ -.slider { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - position: relative; - border-radius: calc(infinity * 1px); - outline: none; -} - -/* Horizontal orientation styles */ -.slider[data-orientation='horizontal'] { - min-width: 5rem; - width: 100%; - height: 1.25rem; -} - -/* Vertical orientation styles */ -.slider[data-orientation='vertical'] { - height: 4.5rem; - width: 1.25rem; -} - -.slider-track { - position: relative; - isolation: isolate; - background-color: oklab(1 0 0 / 0.2); - border-radius: inherit; - overflow: hidden; - user-select: none; - outline: 2px solid transparent; - outline-offset: -2px; - transition: outline-offset 150ms ease-out; - box-shadow: 0 0 0 1px oklab(0 0 0 / 0.05); -} - -/* Horizontal track styles */ -.slider-track[data-orientation='horizontal'] { - width: 100%; - height: 0.1875rem; -} - -/* Vertical track styles */ -.slider-track[data-orientation='vertical'] { - width: 0.1875rem; - height: 100%; -} - -.slider:focus-visible .slider-track { - outline-color: oklab(1 0 0); - outline-offset: 6px; -} - -.slider-thumb { - width: 0.75rem; - height: 0.75rem; - background-color: oklab(1 0 0); - border-radius: calc(infinity * 1px); - user-select: none; - z-index: 10; - box-shadow: - 0 0 0 1px oklab(0 0 0 / 0.1), - 0 1px 3px 0 oklab(0 0 0 / 0.15), - 0 1px 2px -1px oklab(0 0 0 / 0.15); - opacity: 0; - scale: 0.7; - transform-origin: center; - transition-property: opacity, scale; - transition-duration: 150ms; - transition-timing-function: ease-out; -} -.slider:hover .slider-thumb, -.slider:focus-within .slider-thumb { - opacity: 1; - scale: 1; -} -.slider-track[data-orientation='horizontal'] .slider-thumb { - cursor: ew-resize; -} -.slider-track[data-orientation='vertical'] .slider-thumb { - cursor: ns-resize; -} - -.slider-pointer { - display: none; -} - -.slider-progress { - background-color: oklab(1 0 0); - border-radius: inherit; -} - -media-preview-time-display { - font-variant-numeric: tabular-nums; -} - -.popup-animation { - transition-property: transform, scale, opacity, filter; - transition-duration: 200ms; - transform: scale(1); - transform-origin: bottom; - opacity: 1; - filter: blur(0px); -} -.popup-animation[data-starting-style], -.popup-animation[data-ending-style] { - transform: scale(0); - opacity: 0; - filter: blur(8px); -} -.popup-animation[data-instant] { - transition-duration: 0ms; -} - -media-popover { - margin: 0; - border: none; - box-shadow: none; - background: transparent; - padding: 0.75rem 0.25rem; - border-radius: calc(infinity * 1px); -} - -/* Tooltip Component Styles */ -media-tooltip { - margin: 0; - border: none; - box-shadow: none; - background: transparent; - color: oklab(1 0 0); - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; - font-size: 0.75rem; - background-color: oklab(1 0 0 / 0.1); - backdrop-filter: blur(64px) brightness(0.9) saturate(1.5); - box-shadow: - 0 4px 6px -1px oklab(0 0 0 / 0.1), - 0 2px 4px -2px oklab(0 0 0 / 0.1); -} -@media (prefers-reduced-transparency: reduce) { - media-tooltip { - background-color: oklab(0 0 0 / 0.7); - } -} -@media (prefers-contrast: more) { - media-tooltip { - background-color: oklab(0 0 0 / 0.9); - } -} - -.tooltip { - display: none; - white-space: nowrap; -} - -[data-paused] + media-tooltip .play-tooltip, -:not([data-paused]) + media-tooltip .pause-tooltip { - display: block; -} - -[data-fullscreen] + media-tooltip .fullscreen-exit-tooltip, -:not([data-fullscreen]) + media-tooltip .fullscreen-enter-tooltip { - display: block; -}`; - } -} - -/** - * Generate HTML JavaScript imports for the specified skin. - */ -export function generateHTMLJS(skin: Skin): string { - return `import './${skin}.css'; - -// npm install @videojs/html-preview -import '@videojs/html-preview/icons'; -// be sure to import video-provider first for proper context initialization -import '@videojs/html-preview/define/video-provider'; -import '@videojs/html-preview/define/media-container'; -import '@videojs/html-preview/define/media-play-button'; -import '@videojs/html-preview/define/media-mute-button'; -import '@videojs/html-preview/define/media-volume-slider'; -import '@videojs/html-preview/define/media-time-slider'; -import '@videojs/html-preview/define/media-fullscreen-button'; -import '@videojs/html-preview/define/media-duration-display'; -import '@videojs/html-preview/define/media-current-time-display'; -import '@videojs/html-preview/define/media-preview-time-display'; -import '@videojs/html-preview/define/media-popover'; -import '@videojs/html-preview/define/media-tooltip';`; -} From 3b85cffbc2567df581d24b9f4408178b48291070 Mon Sep 17 00:00:00 2001 From: Darius Cepulis Date: Wed, 4 Mar 2026 09:15:44 -0600 Subject: [PATCH 2/2] fix(site): use correct videoFeatures import in hero player The PR used `features.video` which doesn't exist in the current API. The correct export is `videoFeatures` from `@videojs/react/video`. Co-Authored-By: Claude Opus 4.6 --- site/src/components/HomePageDemo/Base.tsx | 6 +++--- site/src/components/home/HeroVideo.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/src/components/HomePageDemo/Base.tsx b/site/src/components/HomePageDemo/Base.tsx index b7366232a..6ab6830c1 100644 --- a/site/src/components/HomePageDemo/Base.tsx +++ b/site/src/components/HomePageDemo/Base.tsx @@ -25,11 +25,11 @@ function generateReactCode(skin: Skin): string { const skinComponent = skin === 'frosted' ? 'VideoSkin' : 'MinimalVideoSkin'; const skinCss = skin === 'frosted' ? 'skin' : 'minimal-skin'; - return `import { createPlayer, features, Poster } from '@videojs/react'; -import { ${skinComponent}, Video } from '@videojs/react/video'; + return `import { createPlayer, Poster } from '@videojs/react'; +import { ${skinComponent}, Video, videoFeatures } from '@videojs/react/video'; import '@videojs/react/video/${skinCss}.css'; -const Player = createPlayer({ features: [...features.video] }); +const Player = createPlayer({ features: [...videoFeatures] }); export function VideoPlayer() { return ( diff --git a/site/src/components/home/HeroVideo.tsx b/site/src/components/home/HeroVideo.tsx index f02cf2756..e4feb8228 100644 --- a/site/src/components/home/HeroVideo.tsx +++ b/site/src/components/home/HeroVideo.tsx @@ -1,12 +1,12 @@ import { useStore } from '@nanostores/react'; -import { createPlayer, features, Poster } from '@videojs/react'; -import { MinimalVideoSkin, Video, VideoSkin } from '@videojs/react/video'; +import { createPlayer, Poster } from '@videojs/react'; +import { MinimalVideoSkin, Video, VideoSkin, videoFeatures } from '@videojs/react/video'; import { VJS8_DEMO_VIDEO } from '@/consts'; import { skin } from '@/stores/homePageDemos'; import '@videojs/react/video/skin.css'; import '@videojs/react/video/minimal-skin.css'; -const Player = createPlayer({ features: [...features.video] }); +const Player = createPlayer({ features: [...videoFeatures] }); export default function HeroVideo({ className, poster }: { className?: string; poster: string }) { const $skin = useStore(skin);